leaderboard admin guru

This commit is contained in:
RetasyaSalsabila 2026-03-11 21:25:21 +07:00
parent 62d15959a6
commit 2835406564
4 changed files with 535 additions and 60 deletions

View File

@ -4,15 +4,50 @@
use App\Http\Controllers\Controller;
use App\Models\Leaderboard;
use App\Models\Kelas;
use Carbon\Carbon;
use Illuminate\Http\Request;
class LeaderboardController extends Controller
{
public function index()
public function index(Request $request)
{
$leaderboards = Leaderboard::orderBy('ranking')
->orderByDesc('total_exp')
->paginate(10);
$now = Carbon::now();
$semester = $request->input('semester', $now->month >= 7 ? '1' : '2');
$tahunAjaran = $request->input('tahun_ajaran', $now->month >= 7
? $now->year . '/' . ($now->year + 1)
: ($now->year - 1) . '/' . $now->year);
$idKelas = $request->input('id_kelas');
return view('admin.leaderboard.index', compact('leaderboards'));
$kelasList = Kelas::orderBy('tingkat')->orderBy('nama_kelas')->get();
$query = Leaderboard::with(['siswa', 'kelas'])
->where('semester', $semester)
->where('tahun_ajaran', $tahunAjaran);
if ($idKelas) {
$query->where('id_kelas', $idKelas);
}
$leaderboard = $query->orderBy('total_exp', 'desc')->get()
->map(function ($item, $i) {
return [
'ranking' => $i + 1,
'nama' => optional($item->siswa)->nama ?? '-',
'nisn' => optional($item->siswa)->nisn ?? '-',
'nama_kelas' => optional($item->kelas)->nama_kelas ?? '-',
'exp' => $item->total_exp,
];
});
// Tahun ajaran list untuk dropdown (5 tahun ke belakang)
$tahunList = [];
for ($y = $now->year; $y >= $now->year - 4; $y--) {
$tahunList[] = $y . '/' . ($y + 1);
}
return view('admin.leaderboard.index', compact(
'leaderboard', 'kelasList', 'semester', 'tahunAjaran', 'idKelas', 'tahunList'
));
}
}
}

View File

@ -4,15 +4,64 @@
use App\Http\Controllers\Controller;
use App\Models\Leaderboard;
use App\Models\Kelas;
use App\Models\Mengajar;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
class LeaderboardController extends Controller
{
public function index()
public function index(Request $request)
{
$leaderboards = Leaderboard::orderBy('ranking')
->orderByDesc('total_exp')
->paginate(10);
/** @var \App\Models\Guru $guru */
$guru = Auth::guard('guru')->user();
return view('admin.leaderboard.index', compact('leaderboards'));
$now = Carbon::now();
$semester = $request->input('semester', $now->month >= 7 ? '1' : '2');
$tahunAjaran = $request->input('tahun_ajaran', $now->month >= 7
? $now->year . '/' . ($now->year + 1)
: ($now->year - 1) . '/' . $now->year);
// Kelas yang diajar guru ini
$idKelasList = Mengajar::where('id_guru', $guru->id_guru)
->pluck('id_kelas')
->unique()
->toArray();
$idKelas = $request->input('id_kelas', $idKelasList[0] ?? null);
$kelasList = Kelas::whereIn('id_kelas', $idKelasList)
->orderBy('tingkat')->orderBy('nama_kelas')
->get();
$leaderboard = collect();
if ($idKelas) {
$leaderboard = Leaderboard::with(['siswa', 'kelas'])
->where('id_kelas', $idKelas)
->where('semester', $semester)
->where('tahun_ajaran', $tahunAjaran)
->orderBy('total_exp', 'desc')
->get()
->map(function ($item, $i) {
return [
'ranking' => $i + 1,
'nama' => optional($item->siswa)->nama ?? '-',
'nisn' => optional($item->siswa)->nisn ?? '-',
'nama_kelas' => optional($item->kelas)->nama_kelas ?? '-',
'exp' => $item->total_exp,
];
});
}
$tahunList = [];
for ($y = $now->year; $y >= $now->year - 4; $y--) {
$tahunList[] = $y . '/' . ($y + 1);
}
return view('guru.leaderboard.index', compact(
'leaderboard', 'kelasList', 'semester', 'tahunAjaran', 'idKelas', 'tahunList'
));
}
}
}

View File

@ -2,33 +2,229 @@
@section('title', 'Leaderboard')
@push('styles')
<style>
.page-title { font-size: 28px; font-weight: 800; margin-top: -20px; margin-bottom: 6px; }
.page-subtitle { font-size: 14px; color: #64748b; margin-bottom: 24px; }
.filter-card {
background: white;
border-radius: 16px;
border: 2px solid #e5e5e5;
padding: 18px 22px;
margin-bottom: 24px;
display: flex;
flex-wrap: wrap;
gap: 14px;
align-items: flex-end;
}
.filter-group { display: flex; flex-direction: column; gap: 5px; }
.filter-group label { font-size: 12px; font-weight: 600; color: #64748b; }
.filter-group select {
border: 2px solid #e2e8f0;
border-radius: 10px;
padding: 7px 12px;
font-size: 13px;
font-family: 'Poppins', sans-serif;
color: #1e293b;
background: white;
cursor: pointer;
}
.btn-filter {
background: #2b8ef3;
color: white;
border: none;
border-radius: 10px;
padding: 9px 20px;
font-size: 13px;
font-weight: 600;
font-family: 'Poppins', sans-serif;
cursor: pointer;
}
/* Podium */
.podium-wrap {
display: flex;
align-items: flex-end;
justify-content: center;
gap: 12px;
margin-bottom: 28px;
}
.podium-item { display: flex; flex-direction: column; align-items: center; gap: 8px; }
.podium-avatar {
width: 56px; height: 56px;
border-radius: 50%;
display: flex; align-items: center; justify-content: center;
font-size: 22px; font-weight: 800; color: white;
position: relative;
}
.podium-crown { position: absolute; top: -16px; font-size: 18px; }
.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); }
.rank-3 .podium-avatar { background: linear-gradient(135deg,#f97316,#ea580c); }
.podium-name { font-size: 13px; font-weight: 700; color: #1e293b; text-align: center; max-width: 90px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.podium-kelas { font-size: 11px; color: #94a3b8; }
.podium-exp { font-size: 12px; color: #64748b; }
.podium-bar { border-radius: 12px 12px 0 0; width: 80px; display: flex; align-items: center; justify-content: center; font-size: 20px; font-weight: 800; color: white; }
.rank-1 .podium-bar { height: 80px; background: linear-gradient(135deg,#f59e0b,#d97706); }
.rank-2 .podium-bar { height: 60px; background: linear-gradient(135deg,#94a3b8,#64748b); }
.rank-3 .podium-bar { height: 44px; background: linear-gradient(135deg,#f97316,#ea580c); }
/* Tabel */
.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; }
.lb-row { display: flex; align-items: center; gap: 14px; padding: 11px 14px; border-radius: 12px; margin-bottom: 6px; transition: background 0.15s; }
.lb-row:hover { background: #f8fafc; }
.lb-rank {
width: 32px; height: 32px; border-radius: 50%;
background: #e2e8f0;
display: flex; align-items: center; justify-content: center;
font-size: 13px; font-weight: 700; color: #64748b; flex-shrink: 0;
}
.lb-rank.gold { background: #fef3c7; color: #d97706; }
.lb-rank.silver { background: #f1f5f9; color: #64748b; }
.lb-rank.bronze { background: #ffedd5; color: #ea580c; }
.lb-nama { flex: 1; font-size: 14px; font-weight: 600; color: #1e293b; }
.lb-nisn { font-size: 12px; color: #94a3b8; }
.lb-kelas { font-size: 12px; color: #64748b; background: #f1f5f9; padding: 2px 8px; border-radius: 99px; }
.lb-exp { font-size: 14px; font-weight: 700; color: #2b8ef3; }
.semester-badge { display: inline-block; background: #e0f2fe; color: #0369a1; font-size: 12px; font-weight: 700; padding: 4px 12px; border-radius: 99px; margin-bottom: 20px; }
.empty-state { text-align: center; padding: 40px 20px; color: #94a3b8; }
</style>
@endpush
@section('content')
<h3 class="mb-4">🏆 Leaderboard</h3>
<h3 class="page-title">🏅 Leaderboard</h3>
<p class="page-subtitle">Peringkat siswa berdasarkan total EXP yang dikumpulkan.</p>
<table class="table table-striped bg-white">
<thead>
<tr>
<th>Ranking</th>
<th>NISN</th>
<th>Total EXP</th>
<th>Semester</th>
<th>Tahun Ajaran</th>
</tr>
</thead>
<tbody>
@foreach($leaderboards as $lb)
<tr>
<td>{{ $lb->ranking }}</td>
<td>{{ $lb->nisn }}</td>
<td>{{ $lb->total_exp }}</td>
<td>{{ $lb->semester }}</td>
<td>{{ $lb->tahun_ajaran }}</td>
</tr>
{{-- Filter --}}
<form method="GET" action="{{ route('admin.leaderboard.index') }}">
<div class="filter-card">
<div class="filter-group">
<label>Semester</label>
<select name="semester">
<option value="1" {{ $semester == '1' ? 'selected' : '' }}>Semester 1</option>
<option value="2" {{ $semester == '2' ? 'selected' : '' }}>Semester 2</option>
</select>
</div>
<div class="filter-group">
<label>Tahun Ajaran</label>
<select name="tahun_ajaran">
@foreach($tahunList as $thn)
<option value="{{ $thn }}" {{ $tahunAjaran == $thn ? 'selected' : '' }}>{{ $thn }}</option>
@endforeach
</select>
</div>
<div class="filter-group">
<label>Kelas</label>
<select name="id_kelas">
<option value="">Semua Kelas</option>
@foreach($kelasList as $kelas)
<option value="{{ $kelas->id_kelas }}" {{ $idKelas == $kelas->id_kelas ? 'selected' : '' }}>
{{ $kelas->nama_kelas }}
</option>
@endforeach
</select>
</div>
<button type="submit" class="btn-filter">Tampilkan</button>
</div>
</form>
<span class="semester-badge">Semester {{ $semester }} · {{ $tahunAjaran }}{{ $idKelas ? ' · ' . ($kelasList->firstWhere('id_kelas', $idKelas)?->nama_kelas ?? '') : ' · Semua Kelas' }}</span>
@if($leaderboard->isEmpty())
<div class="empty-state">
<div style="font-size:52px;margin-bottom:12px">📊</div>
<p style="font-size:15px;font-weight:600;color:#475569">Belum ada data leaderboard.</p>
<p style="font-size:13px">Belum ada siswa yang menyelesaikan challenge pada periode ini.</p>
</div>
@else
{{-- Podium --}}
@php
$first = $leaderboard->firstWhere('ranking', 1);
$second = $leaderboard->firstWhere('ranking', 2);
$third = $leaderboard->firstWhere('ranking', 3);
@endphp
@if($first)
<div class="podium-wrap">
@if($second)
<div class="podium-item rank-2">
<div class="podium-avatar">{{ strtoupper(substr($second['nama'],0,1)) }}</div>
<div class="podium-name">{{ $second['nama'] }}</div>
<div class="podium-kelas">{{ $second['nama_kelas'] }}</div>
<div class="podium-exp"> {{ number_format($second['exp']) }}</div>
<div class="podium-bar">2</div>
</div>
@endif
<div class="podium-item rank-1">
<div class="podium-avatar">
<span class="podium-crown">👑</span>
{{ strtoupper(substr($first['nama'],0,1)) }}
</div>
<div class="podium-name">{{ $first['nama'] }}</div>
<div class="podium-kelas">{{ $first['nama_kelas'] }}</div>
<div class="podium-exp"> {{ number_format($first['exp']) }}</div>
<div class="podium-bar">1</div>
</div>
@if($third)
<div class="podium-item rank-3">
<div class="podium-avatar">{{ strtoupper(substr($third['nama'],0,1)) }}</div>
<div class="podium-name">{{ $third['nama'] }}</div>
<div class="podium-kelas">{{ $third['nama_kelas'] }}</div>
<div class="podium-exp"> {{ number_format($third['exp']) }}</div>
<div class="podium-bar">3</div>
</div>
@endif
</div>
@endif
{{-- Tabel --}}
<div class="custom-card">
<p class="section-title">📋 Semua Peringkat ({{ $leaderboard->count() }} siswa)</p>
@foreach($leaderboard as $item)
@php
$rankClass = match($item['ranking']) { 1=>'gold', 2=>'silver', 3=>'bronze', default=>'' };
@endphp
<div class="lb-row">
<div class="lb-rank {{ $rankClass }}">
@if($item['ranking']===1) 🥇
@elseif($item['ranking']===2) 🥈
@elseif($item['ranking']===3) 🥉
@else {{ $item['ranking'] }}
@endif
</div>
<div style="flex:1">
<div class="lb-nama">{{ $item['nama'] }}</div>
<div class="lb-nisn">{{ $item['nisn'] }}</div>
</div>
<span class="lb-kelas">{{ $item['nama_kelas'] }}</span>
<div class="lb-exp"> {{ number_format($item['exp']) }}</div>
</div>
@endforeach
</tbody>
</table>
</div>
{{ $leaderboards->links() }}
@endif
@endsection
@endsection

View File

@ -2,33 +2,228 @@
@section('title', 'Leaderboard')
@push('styles')
<style>
.page-title { font-size: 28px; font-weight: 800; margin-top: -20px; margin-bottom: 6px; }
.page-subtitle { font-size: 14px; color: #64748b; margin-bottom: 24px; }
.filter-card {
background: white;
border-radius: 16px;
border: 2px solid #e5e5e5;
padding: 18px 22px;
margin-bottom: 24px;
display: flex;
flex-wrap: wrap;
gap: 14px;
align-items: flex-end;
}
.filter-group { display: flex; flex-direction: column; gap: 5px; }
.filter-group label { font-size: 12px; font-weight: 600; color: #64748b; }
.filter-group select {
border: 2px solid #e2e8f0;
border-radius: 10px;
padding: 7px 12px;
font-size: 13px;
font-family: 'Poppins', sans-serif;
color: #1e293b;
background: white;
cursor: pointer;
}
.btn-filter {
background: #2b8ef3;
color: white;
border: none;
border-radius: 10px;
padding: 9px 20px;
font-size: 13px;
font-weight: 600;
font-family: 'Poppins', sans-serif;
cursor: pointer;
}
.podium-wrap { display: flex; align-items: flex-end; justify-content: center; gap: 12px; margin-bottom: 28px; }
.podium-item { display: flex; flex-direction: column; align-items: center; gap: 8px; }
.podium-avatar {
width: 56px; height: 56px; border-radius: 50%;
display: flex; align-items: center; justify-content: center;
font-size: 22px; font-weight: 800; color: white; position: relative;
}
.podium-crown { position: absolute; top: -16px; font-size: 18px; }
.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); }
.rank-3 .podium-avatar { background: linear-gradient(135deg,#f97316,#ea580c); }
.podium-name { font-size: 13px; font-weight: 700; color: #1e293b; text-align: center; max-width: 90px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.podium-exp { font-size: 12px; color: #64748b; }
.podium-bar { border-radius: 12px 12px 0 0; width: 80px; display: flex; align-items: center; justify-content: center; font-size: 20px; font-weight: 800; color: white; }
.rank-1 .podium-bar { height: 80px; background: linear-gradient(135deg,#f59e0b,#d97706); }
.rank-2 .podium-bar { height: 60px; background: linear-gradient(135deg,#94a3b8,#64748b); }
.rank-3 .podium-bar { height: 44px; background: linear-gradient(135deg,#f97316,#ea580c); }
.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; }
.lb-row { display: flex; align-items: center; gap: 14px; padding: 11px 14px; border-radius: 12px; margin-bottom: 6px; transition: background 0.15s; }
.lb-row:hover { background: #f8fafc; }
.lb-rank {
width: 32px; height: 32px; border-radius: 50%;
background: #e2e8f0;
display: flex; align-items: center; justify-content: center;
font-size: 13px; font-weight: 700; color: #64748b; flex-shrink: 0;
}
.lb-rank.gold { background: #fef3c7; color: #d97706; }
.lb-rank.silver { background: #f1f5f9; color: #64748b; }
.lb-rank.bronze { background: #ffedd5; color: #ea580c; }
.lb-nama { flex: 1; font-size: 14px; font-weight: 600; color: #1e293b; }
.lb-nisn { font-size: 12px; color: #94a3b8; }
.lb-exp { font-size: 14px; font-weight: 700; color: #2b8ef3; }
.semester-badge { display: inline-block; background: #e0f2fe; color: #0369a1; font-size: 12px; font-weight: 700; padding: 4px 12px; border-radius: 99px; margin-bottom: 20px; }
.empty-state { text-align: center; padding: 40px 20px; color: #94a3b8; }
.no-kelas-box {
background: #fff7ed;
border: 2px solid #fed7aa;
border-radius: 16px;
padding: 24px;
text-align: center;
color: #c2410c;
font-size: 14px;
font-weight: 500;
}
</style>
@endpush
@section('content')
<h3 class="mb-4">🏆 Leaderboard</h3>
<h3 class="page-title">🏅 Leaderboard</h3>
<p class="page-subtitle">Peringkat siswa di kelas yang Anda ajar.</p>
<table class="table table-striped bg-white">
<thead>
<tr>
<th>Ranking</th>
<th>NISN</th>
<th>Total EXP</th>
<th>Semester</th>
<th>Tahun Ajaran</th>
</tr>
</thead>
<tbody>
@foreach($leaderboards as $lb)
<tr>
<td>{{ $lb->ranking }}</td>
<td>{{ $lb->nisn }}</td>
<td>{{ $lb->total_exp }}</td>
<td>{{ $lb->semester }}</td>
<td>{{ $lb->tahun_ajaran }}</td>
</tr>
@if($kelasList->isEmpty())
<div class="no-kelas-box">
⚠️ Anda belum mengajar kelas manapun. Hubungi admin untuk mengatur jadwal mengajar.
</div>
@else
{{-- Filter --}}
<form method="GET" action="{{ route('guru.leaderboard.index') }}">
<div class="filter-card">
<div class="filter-group">
<label>Kelas</label>
<select name="id_kelas">
@foreach($kelasList as $kelas)
<option value="{{ $kelas->id_kelas }}" {{ $idKelas == $kelas->id_kelas ? 'selected' : '' }}>
{{ $kelas->nama_kelas }}
</option>
@endforeach
</select>
</div>
<div class="filter-group">
<label>Semester</label>
<select name="semester">
<option value="1" {{ $semester == '1' ? 'selected' : '' }}>Semester 1</option>
<option value="2" {{ $semester == '2' ? 'selected' : '' }}>Semester 2</option>
</select>
</div>
<div class="filter-group">
<label>Tahun Ajaran</label>
<select name="tahun_ajaran">
@foreach($tahunList as $thn)
<option value="{{ $thn }}" {{ $tahunAjaran == $thn ? 'selected' : '' }}>{{ $thn }}</option>
@endforeach
</select>
</div>
<button type="submit" class="btn-filter">Tampilkan</button>
</div>
</form>
@php $namaKelas = $kelasList->firstWhere('id_kelas', $idKelas)?->nama_kelas ?? '-'; @endphp
<span class="semester-badge">{{ $namaKelas }} · Semester {{ $semester }} · {{ $tahunAjaran }}</span>
@if($leaderboard->isEmpty())
<div class="empty-state">
<div style="font-size:52px;margin-bottom:12px">📊</div>
<p style="font-size:15px;font-weight:600;color:#475569">Belum ada data leaderboard.</p>
<p style="font-size:13px">Belum ada siswa yang menyelesaikan challenge pada periode ini.</p>
</div>
@else
{{-- Podium --}}
@php
$first = $leaderboard->firstWhere('ranking', 1);
$second = $leaderboard->firstWhere('ranking', 2);
$third = $leaderboard->firstWhere('ranking', 3);
@endphp
@if($first)
<div class="podium-wrap">
@if($second)
<div class="podium-item rank-2">
<div class="podium-avatar">{{ strtoupper(substr($second['nama'],0,1)) }}</div>
<div class="podium-name">{{ $second['nama'] }}</div>
<div class="podium-exp"> {{ number_format($second['exp']) }}</div>
<div class="podium-bar">2</div>
</div>
@endif
<div class="podium-item rank-1">
<div class="podium-avatar">
<span class="podium-crown">👑</span>
{{ strtoupper(substr($first['nama'],0,1)) }}
</div>
<div class="podium-name">{{ $first['nama'] }}</div>
<div class="podium-exp"> {{ number_format($first['exp']) }}</div>
<div class="podium-bar">1</div>
</div>
@if($third)
<div class="podium-item rank-3">
<div class="podium-avatar">{{ strtoupper(substr($third['nama'],0,1)) }}</div>
<div class="podium-name">{{ $third['nama'] }}</div>
<div class="podium-exp"> {{ number_format($third['exp']) }}</div>
<div class="podium-bar">3</div>
</div>
@endif
</div>
@endif
{{-- Tabel --}}
<div class="custom-card">
<p class="section-title">📋 Semua Peringkat ({{ $leaderboard->count() }} siswa)</p>
@foreach($leaderboard as $item)
@php $rankClass = match($item['ranking']) { 1=>'gold', 2=>'silver', 3=>'bronze', default=>'' }; @endphp
<div class="lb-row">
<div class="lb-rank {{ $rankClass }}">
@if($item['ranking']===1) 🥇
@elseif($item['ranking']===2) 🥈
@elseif($item['ranking']===3) 🥉
@else {{ $item['ranking'] }}
@endif
</div>
<div style="flex:1">
<div class="lb-nama">{{ $item['nama'] }}</div>
<div class="lb-nisn">{{ $item['nisn'] }}</div>
</div>
<div class="lb-exp"> {{ number_format($item['exp']) }}</div>
</div>
@endforeach
</tbody>
</table>
</div>
{{ $leaderboards->links() }}
@endif
@endif
@endsection
@endsection