diff --git a/app/Http/Controllers/Admin/ProfileController.php b/app/Http/Controllers/Admin/ProfileController.php index 87e7a3a..8341ba1 100644 --- a/app/Http/Controllers/Admin/ProfileController.php +++ b/app/Http/Controllers/Admin/ProfileController.php @@ -7,7 +7,6 @@ use Illuminate\Support\Facades\Auth; use Illuminate\Support\Facades\Hash; use Illuminate\Support\Facades\Storage; -use App\Models\Admin; class ProfileController extends Controller { @@ -17,7 +16,7 @@ public function edit() return view('admin.profile.edit', compact('admin')); } - public function update(Request $request) + public function updateAjax(Request $request) { $admin = Auth::guard('admin')->user(); @@ -41,17 +40,23 @@ public function update(Request $request) $data['password'] = Hash::make($request->password); } + $fotoUrl = null; if ($request->hasFile('foto_profil')) { if ($admin->foto_profil && Storage::disk('public')->exists($admin->foto_profil)) { Storage::disk('public')->delete($admin->foto_profil); } $path = $request->file('foto_profil')->store('foto_profil/admin', 'public'); $data['foto_profil'] = $path; + $fotoUrl = Storage::url($path); } $admin->update($data); - return redirect()->route('admin.profile.edit') - ->with('success', 'Profil berhasil diperbarui!'); + return response()->json([ + 'success' => true, + 'message' => 'Profil berhasil diperbarui!', + 'foto_url' => $fotoUrl ?? ($admin->foto_profil ? Storage::url($admin->foto_profil) : null), + 'username' => $admin->fresh()->username, + ]); } } \ No newline at end of file diff --git a/app/Http/Controllers/Guru/ProfileController.php b/app/Http/Controllers/Guru/ProfileController.php index 31fb263..17f1f19 100644 --- a/app/Http/Controllers/Guru/ProfileController.php +++ b/app/Http/Controllers/Guru/ProfileController.php @@ -10,13 +10,7 @@ class ProfileController extends Controller { - public function edit() - { - $guru = Auth::guard('guru')->user(); - return view('guru.profile.edit', compact('guru')); - } - - public function update(Request $request) + public function updateAjax(Request $request) { $guru = Auth::guard('guru')->user(); @@ -31,7 +25,8 @@ public function update(Request $request) 'foto_profil.max' => 'Ukuran foto maksimal 2MB.', ]); - $data = []; + $data = []; + $fotoUrl = null; if ($request->filled('password')) { $data['password'] = Hash::make($request->password); @@ -43,13 +38,17 @@ public function update(Request $request) } $path = $request->file('foto_profil')->store('foto_profil/guru', 'public'); $data['foto_profil'] = $path; + $fotoUrl = Storage::url($path); } if (!empty($data)) { $guru->update($data); } - return redirect()->route('guru.profile.edit') - ->with('success', 'Profil berhasil diperbarui!'); + return response()->json([ + 'success' => true, + 'message' => 'Profil berhasil diperbarui!', + 'foto_url' => $fotoUrl ?? ($guru->foto_profil ? Storage::url($guru->foto_profil) : null), + ]); } } \ No newline at end of file diff --git a/app/Http/Controllers/Siswa/LeaderboardController.php b/app/Http/Controllers/Siswa/LeaderboardController.php index 8f437dd..d07582d 100644 --- a/app/Http/Controllers/Siswa/LeaderboardController.php +++ b/app/Http/Controllers/Siswa/LeaderboardController.php @@ -5,11 +5,12 @@ use App\Http\Controllers\Controller; use App\Models\Leaderboard; use Illuminate\Support\Facades\Auth; +use Illuminate\Support\Facades\Storage; use Carbon\Carbon; class LeaderboardController extends Controller { - public function index() + private function getData() { $siswa = Auth::guard('siswa')->user(); @@ -19,7 +20,6 @@ public function index() ? $now->year . '/' . ($now->year + 1) : ($now->year - 1) . '/' . $now->year; - // Top leaderboard kelas siswa ini $leaderboard = Leaderboard::with('siswa') ->where('id_kelas', $siswa->id_kelas) ->where('semester', $semester) @@ -27,20 +27,33 @@ public function index() ->orderBy('total_exp', 'desc') ->get() ->map(function ($item, $i) { + $fotoProfil = optional($item->siswa)->foto_profil; return [ - 'ranking' => $i + 1, - 'nama' => optional($item->siswa)->nama ?? '-', - 'nisn' => optional($item->siswa)->nisn ?? '-', - 'exp' => $item->total_exp, - 'id_siswa'=> $item->id_siswa, + 'ranking' => $i + 1, + 'nama' => optional($item->siswa)->nama ?? '-', + 'nisn' => optional($item->siswa)->nisn ?? '-', + 'exp' => $item->total_exp, + 'id_siswa' => $item->id_siswa, + 'foto_profil' => $fotoProfil, + 'foto_url' => $fotoProfil ? Storage::url($fotoProfil) : null, ]; }); - // Posisi siswa yang login $myRank = $leaderboard->firstWhere('id_siswa', $siswa->id_siswa); - return view('siswa.leaderboard.index', compact( - 'leaderboard', 'myRank', 'semester', 'tahunAjaran' - )); + return compact('leaderboard', 'myRank', 'semester', 'tahunAjaran'); + } + + public function index() + { + $data = $this->getData(); + return view('siswa.leaderboard.index', $data); + } + + // Endpoint JSON untuk polling real-time + public function json() + { + $data = $this->getData(); + return response()->json($data); } } \ No newline at end of file diff --git a/app/Http/Controllers/Siswa/ProfileController.php b/app/Http/Controllers/Siswa/ProfileController.php index 39b4699..eeadbaa 100644 --- a/app/Http/Controllers/Siswa/ProfileController.php +++ b/app/Http/Controllers/Siswa/ProfileController.php @@ -10,13 +10,7 @@ class ProfileController extends Controller { - public function edit() - { - $siswa = Auth::guard('siswa')->user(); - return view('siswa.profile.edit', compact('siswa')); - } - - public function update(Request $request) + public function updateAjax(Request $request) { $siswa = Auth::guard('siswa')->user(); @@ -31,28 +25,30 @@ public function update(Request $request) 'foto_profil.max' => 'Ukuran foto maksimal 2MB.', ]); - $data = []; + $data = []; + $fotoUrl = null; - // Update password if ($request->filled('password')) { $data['password'] = Hash::make($request->password); } - // Update foto profil if ($request->hasFile('foto_profil')) { - // Hapus foto lama jika ada if ($siswa->foto_profil && Storage::disk('public')->exists($siswa->foto_profil)) { Storage::disk('public')->delete($siswa->foto_profil); } $path = $request->file('foto_profil')->store('foto_profil/siswa', 'public'); $data['foto_profil'] = $path; + $fotoUrl = Storage::url($path); } if (!empty($data)) { $siswa->update($data); } - return redirect()->route('siswa.profile.edit') - ->with('success', 'Profil berhasil diperbarui!'); + return response()->json([ + 'success' => true, + 'message' => 'Profil berhasil diperbarui!', + 'foto_url' => $fotoUrl ?? ($siswa->foto_profil ? Storage::url($siswa->foto_profil) : null), + ]); } } \ No newline at end of file diff --git a/resources/views/admin/dashboard.blade.php b/resources/views/admin/dashboard.blade.php index d9e9b2f..423dcb3 100644 --- a/resources/views/admin/dashboard.blade.php +++ b/resources/views/admin/dashboard.blade.php @@ -2,28 +2,270 @@ @section('title', 'Dashboard Admin') +@push('styles') + +@endpush + @section('content') -
-

Dashboard Admin

-
-
-
-
-
Total Guru
-

12

-
-
-
+@php + use Carbon\Carbon; + $greeting = Carbon::now()->hour < 12 ? 'Selamat Pagi' : (Carbon::now()->hour < 17 ? 'Selamat Siang' : 'Selamat Malam'); +@endphp -
-
-
-
Total Siswa
-

230

-
-
-
+
+
{{ $greeting }}, {{ Auth::guard('admin')->user()->username ?? 'Admin' }} 👋
+
{{ Carbon::now()->isoFormat('dddd, D MMMM Y') }}
+
+ +{{-- STAT CARDS --}} +
+
+
👨‍🏫
+
Total Guru
+
{{ $totalGuru }}
+
+
+
🏫
+
Total Kelas
+
{{ $totalKelas }}
+
+
+
👨‍🎓
+
Total Siswa
+
{{ $totalSiswa }}
+
+
+
📚
+
Total Mapel
+
{{ $totalMapel }}
+ +{{-- BOTTOM: Chart + Challenge --}} +
+ + {{-- Bar Chart --}} +
+
+
Total Siswa Berdasarkan Kelas
+ {{ $chartData->count() }} Kelas +
+
+ +
+
+ + {{-- Challenge Terbaru --}} +
+
+
Challenge Terbaru
+ Lihat Semua → +
+ + @forelse($latestChallenges as $ch) +
+
+ {{ $ch->kelas->pluck('nama_kelas')->join(', ') ?: 'Semua Kelas' }} +
+
{{ $ch->judul_challenge }}
+
+ {{ $ch->soal->count() }} Soal + @if($ch->tenggat_waktu) + · Tenggat: {{ \Carbon\Carbon::parse($ch->tenggat_waktu)->isoFormat('D MMM Y') }} + @endif +
+
+ @empty +
+ Belum ada challenge dibuat. +
+ @endforelse + + + + Tambahkan Challenge Baru + +
+ +
+ @endsection + +@push('scripts') + + +@endpush \ No newline at end of file diff --git a/resources/views/admin/layouts/app.blade.php b/resources/views/admin/layouts/app.blade.php index f4f9813..b0d8469 100644 --- a/resources/views/admin/layouts/app.blade.php +++ b/resources/views/admin/layouts/app.blade.php @@ -3,44 +3,109 @@ + @yield('title', 'Admin Panel') @stack('styles') @@ -48,7 +113,7 @@
-
+{{-- PROFILE MODAL --}} +
+
+ + + + +
+ @csrf +
+
+ @if($admin->foto_profil) + + @else + + + @endif +
+
+

JPG, PNG, WEBP · Maks. 2MB

+ + +
+
+ + + + + +
+
+
+ + @stack('scripts') \ No newline at end of file diff --git a/resources/views/guru/dashboard.blade.php b/resources/views/guru/dashboard.blade.php index f79f39a..9606266 100644 --- a/resources/views/guru/dashboard.blade.php +++ b/resources/views/guru/dashboard.blade.php @@ -2,47 +2,192 @@ @section('title', 'Dashboard Guru') +@push('styles') + +@endpush + @section('content') -
-

Dashboard Guru

+@php + use Carbon\Carbon; + use App\Models\Mengajar; + $guru = Auth::guard('guru')->user(); + $greeting = Carbon::now()->hour < 12 ? 'Selamat Pagi' : (Carbon::now()->hour < 17 ? 'Selamat Siang' : 'Selamat Malam'); -
+ // Ambil mengajar dengan relasi mapel & kelas + $mengajars = Mengajar::with(['mapel', 'kelas']) + ->where('id_guru', $guru->id_guru) + ->get(); +@endphp -
-
-
-
Total Kelas Diampu
-

- {{ $totalKelas }} -

-
-
+
+
{{ $greeting }}, {{ $guru->nama ?? 'Guru' }} 👋
+
{{ Carbon::now()->isoFormat('dddd, D MMMM Y') }}
+
+ +{{-- STAT CARDS --}} +
+
+
🏫
+
Kelas Diampu
+
{{ $totalKelas }}
+
+
+
📚
+
Mata Pelajaran
+
{{ $totalMapel }}
+
+
+
👨‍🎓
+
Siswa Diajar
+
{{ $totalSiswa }}
+
+
+ +{{-- INFO GRID --}} +
+ + {{-- Daftar Mengajar --}} +
+
+
Mata Pelajaran & Kelas
+ Lihat Semua →
-
-
-
-
Total Mata Pelajaran
-

- {{ $totalMapel }} -

-
-
+ @php $dotColors = ['#2b8ef3','#22c55e','#f97316','#a855f7','#ec4899','#eab308']; @endphp + + @forelse($mengajars->take(6) as $i => $m) +
+
+
{{ optional($m->mapel)->nama_mapel ?? '-' }}
+
{{ optional($m->kelas)->nama_kelas ?? '-' }}
+
+ @empty +
+ Belum ada data mengajar. +
+ @endforelse +
+ + {{-- Quick Links --}} + -@endsection + +@endsection \ No newline at end of file diff --git a/resources/views/guru/layouts/app.blade.php b/resources/views/guru/layouts/app.blade.php index e21e04c..6ad500b 100644 --- a/resources/views/guru/layouts/app.blade.php +++ b/resources/views/guru/layouts/app.blade.php @@ -3,41 +3,81 @@ + @yield('title', 'Panel Guru') @stack('styles') @@ -45,10 +85,8 @@ + +{{-- PROFILE MODAL --}} +
+
+ + + + +
+ @csrf +
+
+ @if($guru->foto_profil) + + @else + + + @endif +
+
+

JPG, PNG, WEBP · Maks. 2MB

+ + +
+
+ + + + + + +
+
+
+ + @stack('scripts') \ No newline at end of file diff --git a/resources/views/siswa/layouts/app.blade.php b/resources/views/siswa/layouts/app.blade.php index 8c8e621..2e5f848 100644 --- a/resources/views/siswa/layouts/app.blade.php +++ b/resources/views/siswa/layouts/app.blade.php @@ -3,153 +3,88 @@ + @yield('title', 'Siswa Panel') @stack('styles') @@ -162,70 +97,52 @@ - - -
-
👋 Hai, {{ Auth::guard('siswa')->user()->nama ?? 'Siswa' }}
-
Notification - {{-- Foto profil atau icon default --}} @php $siswa = Auth::guard('siswa')->user(); @endphp @if($siswa->foto_profil) - - Profil - + Profil @else - +
- +
@endif
@@ -236,23 +153,179 @@ class="topbar-avatar" alt="Profil">
+{{-- PROFILE MODAL --}} +
+
+ + + + + + + + +
+ @csrf + +
+
+ @if($siswa->foto_profil) + + @else + + + + + @endif +
+
+

JPG, PNG, WEBP · Maks. 2MB

+ + +
+
+ + + + + + + + + + + + +
+
+
+ @stack('scripts') diff --git a/resources/views/siswa/leaderboard/index.blade.php b/resources/views/siswa/leaderboard/index.blade.php index 80e330e..7fbc90c 100644 --- a/resources/views/siswa/leaderboard/index.blade.php +++ b/resources/views/siswa/leaderboard/index.blade.php @@ -10,16 +10,9 @@ .podium-wrap { display: flex; align-items: flex-end; justify-content: center; gap: 12px; margin-bottom: 32px; } .podium-item { display: flex; flex-direction: column; align-items: center; gap: 8px; } -.podium-avatar { - border-radius: 50%; - display: flex; align-items: center; justify-content: center; - font-size: 22px; font-weight: 800; color: white; - position: relative; overflow: hidden; flex-shrink: 0; - width: 56px; height: 56px; -} - +.podium-avatar { border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 22px; font-weight: 800; color: white; position: relative; overflow: hidden; flex-shrink: 0; width: 56px; height: 56px; } .podium-avatar img { width: 100%; height: 100%; object-fit: cover; border-radius: 50%; } -.podium-crown { position: absolute; top: -16px; font-size: 18px; z-index: 1; } +.podium-crown { position: absolute; top: -16px; font-size: 18px; z-index: 1; pointer-events: none; } .rank-1 .podium-avatar { background: linear-gradient(135deg,#f59e0b,#d97706); width:68px; height:68px; font-size:26px; } .rank-2 .podium-avatar { background: linear-gradient(135deg,#94a3b8,#64748b); } @@ -36,14 +29,17 @@ .my-rank-banner { background:linear-gradient(135deg,#667eea,#764ba2); border-radius:16px; padding:16px 20px; color:white; display:flex; align-items:center; gap:16px; margin-bottom:20px; } .my-rank-avatar { width:48px; height:48px; border-radius:50%; object-fit:cover; border:2px solid rgba(255,255,255,0.5); flex-shrink:0; } .my-rank-avatar-placeholder { width:48px; height:48px; border-radius:50%; background:rgba(255,255,255,0.2); display:flex; align-items:center; justify-content:center; font-size:20px; font-weight:800; flex-shrink:0; } -.my-rank-num { font-size:36px; font-weight:800; line-height:1; } -.my-rank-info { flex:1; } +.my-rank-num { font-size:36px; font-weight:800; line-height:1; } +.my-rank-info { flex:1; } .my-rank-label { font-size:12px; opacity:0.8; margin-bottom:2px; } .my-rank-nama { font-size:16px; font-weight:700; } .my-rank-exp { font-size:13px; opacity:0.9; } .custom-card { background:white; border-radius:20px; border:2px solid #e5e5e5; padding:22px; } -.section-title { font-size:15px; font-weight:700; color:#1e293b; margin-bottom:16px; } +.section-title { font-size:15px; font-weight:700; color:#1e293b; margin-bottom:16px; display:flex; align-items:center; justify-content:space-between; } + +.live-dot { width:8px; height:8px; border-radius:50%; background:#22c55e; box-shadow:0 0 6px #22c55e; animation: pulse 2s ease-in-out infinite; display:inline-block; margin-right:6px; } +@keyframes pulse { 0%,100%{opacity:1;transform:scale(1)} 50%{opacity:0.5;transform:scale(0.8)} } .lb-row { display:flex; align-items:center; gap:12px; padding:10px 14px; border-radius:12px; margin-bottom:8px; transition:background 0.15s; } .lb-row:hover { background:#f8fafc; } @@ -75,125 +71,172 @@ Semester {{ $semester }} · {{ $tahunAjaran }} -@if($leaderboard->isEmpty()) -
-
📊
-

Belum ada data leaderboard.

-

Kerjakan challenge untuk masuk leaderboard!

+{{-- Container di-render real-time via JavaScript --}} +
+
+
+

Memuat data...

-@else +
- @php - $top3 = $leaderboard->take(3); - $first = $top3->firstWhere('ranking', 1); - $second = $top3->firstWhere('ranking', 2); - $third = $top3->firstWhere('ranking', 3); - @endphp +@endsection - @if($first) -
+@push('scripts') + +@endpush \ No newline at end of file diff --git a/routes/web.php b/routes/web.php index 3fb21d2..da598b6 100644 --- a/routes/web.php +++ b/routes/web.php @@ -88,8 +88,8 @@ return view('admin.notif'); })->name('notif'); - Route::get('/profile', [AdminProfileController::class, 'edit'])->name('profile.edit'); - Route::put('/profile', [AdminProfileController::class, 'update'])->name('profile.update'); + Route::get('/profile', [AdminProfileController::class, 'edit'])->name('profile.edit'); + Route::post('/profile', [AdminProfileController::class, 'updateAjax'])->name('profile.update'); // ── GURU ────────────────────────────────────────────── Route::get('/guru/kelas-by-mapel', [AdminGuruController::class, 'getKelasByMapel']) @@ -154,8 +154,8 @@ Route::get('/tugas/{id}/detail', [GuruMapelController::class, 'detailTugas'])->name('tugas.detail'); Route::delete('/tugas/{id}', [GuruMapelController::class, 'destroyTugas'])->name('tugas.destroy'); - Route::get('/profile', [GuruProfileController::class, 'edit'])->name('profile.edit'); - Route::put('/profile', [GuruProfileController::class, 'update'])->name('profile.update'); + Route::get('/profile', [GuruProfileController::class, 'edit'])->name('profile.edit'); + Route::post('/profile', [GuruProfileController::class, 'updateAjax'])->name('profile.update'); // LOGOUT GURU Route::post('/logout', [GuruLoginController::class, 'logout'])->name('logout'); @@ -185,10 +185,11 @@ //LEADERBOARD SISWA Route::get('/leaderboard', [SiswaLeaderboardController::class, 'index'])->name('leaderboard.index'); + Route::get('/leaderboard/json', [SiswaLeaderboardController::class, 'json'])->name('leaderboard.json'); //PROFILE SISWA - Route::get('/profile', [SiswaProfileController::class, 'edit'])->name('profile.edit'); - Route::put('/profile', [SiswaProfileController::class, 'update'])->name('profile.update'); + Route::get('/profile', [SiswaProfileController::class, 'edit'])->name('profile.edit'); + Route::post('/profile', [SiswaProfileController::class, 'updateAjax'])->name('profile.update'); // LOGOUT SISWA Route::post('/logout', [SiswaLoginController::class, 'logout'])->name('logout');