191 lines
6.7 KiB
PHP
191 lines
6.7 KiB
PHP
<?php
|
||
|
||
namespace App\Http\Controllers\Siswa;
|
||
|
||
use App\Http\Controllers\Controller;
|
||
use App\Models\Leaderboard;
|
||
use App\Models\Siswa;
|
||
use App\Models\SiswaBadge;
|
||
use App\Services\BadgeService;
|
||
use Illuminate\Support\Facades\Auth;
|
||
use Illuminate\Support\Facades\Storage;
|
||
use Illuminate\Support\Facades\DB;
|
||
use Carbon\Carbon;
|
||
|
||
class LeaderboardController extends Controller
|
||
{
|
||
/**
|
||
* Hitung total EXP siswa secara dinamis langsung dari database.
|
||
*
|
||
* Formula:
|
||
* + 1 poin per soal benar di setiap challenge
|
||
* +10 poin per tugas yang dikumpulkan tepat waktu
|
||
* + 5 poin per tugas yang terlambat < 1 hari (grace period)
|
||
* + 3 poin per tugas yang terlambat 2–3 hari
|
||
* - 5 poin per tugas yang terlambat > 3 hari
|
||
* - 5 poin per tugas yang tidak dikumpulkan sama sekali (setelah deadline)
|
||
*/
|
||
private function hitungExpSiswa(int $idSiswa, int $idKelas): int
|
||
{
|
||
$now = Carbon::now();
|
||
|
||
// ── 1. EXP dari challenge (soal yang dijawab benar) ──────────────
|
||
$expChallenge = 0;
|
||
|
||
$pesertaList = DB::table('peserta_challenges')
|
||
->where('id_siswa', $idSiswa)
|
||
->get();
|
||
|
||
foreach ($pesertaList as $peserta) {
|
||
$jawaban = json_decode($peserta->jawaban, true) ?? [];
|
||
$soalList = DB::table('soal_challenge')
|
||
->where('id_challenge', $peserta->id_challenge)
|
||
->get();
|
||
|
||
foreach ($soalList as $soal) {
|
||
$jwb = $jawaban[$soal->id_soal] ?? null;
|
||
if ($jwb && strtoupper($jwb) === strtoupper($soal->jawaban_benar)) {
|
||
$expChallenge += 1; // +1 per soal benar
|
||
}
|
||
}
|
||
}
|
||
|
||
// ── 2. EXP dari tugas ────────────────────────────────────────────
|
||
$semuaTugasIdKelas = DB::table('tugas')
|
||
->join('mengajars', 'tugas.id_mengajar', '=', 'mengajars.id_mengajar')
|
||
->where('mengajars.id_kelas', $idKelas)
|
||
->select('tugas.id_tugas', 'tugas.deadline')
|
||
->get();
|
||
|
||
$expTugas = 0;
|
||
|
||
foreach ($semuaTugasIdKelas as $tugas) {
|
||
$deadline = Carbon::parse($tugas->deadline);
|
||
$pengumpulan = DB::table('pengumpulan_tugas')
|
||
->where('id_tugas', $tugas->id_tugas)
|
||
->where('id_siswa', $idSiswa)
|
||
->first();
|
||
|
||
if ($pengumpulan && $pengumpulan->lampiran_tugas !== null) {
|
||
$tanggalSubmit = Carbon::parse($pengumpulan->tanggal_submit);
|
||
|
||
if ($tanggalSubmit->lessThanOrEqualTo($deadline)) {
|
||
$expTugas += 10; // Tepat waktu → +10
|
||
} else {
|
||
$hariTerlambat = $deadline->diffInDays($tanggalSubmit); // ← dibalik
|
||
|
||
if ($hariTerlambat <= 1) {
|
||
$expTugas += 5; // Terlambat ≤ 1 hari → +5
|
||
} elseif ($hariTerlambat <= 3) {
|
||
$expTugas += 1; // Terlambat 2-3 hari → +3
|
||
} else {
|
||
$expTugas -= 5; // Lewat grace period > 3 hari → dianggap tidak kumpul
|
||
}
|
||
}
|
||
} else {
|
||
// Belum mengumpulkan file sama sekali
|
||
if ($now->greaterThan($deadline)) {
|
||
// Deadline sudah lewat → penalti -5
|
||
$expTugas -= 5;
|
||
}
|
||
// Deadline belum lewat → tidak ada penalti, tidak ada poin
|
||
}
|
||
}
|
||
|
||
// Total EXP tidak bisa negatif
|
||
return max(0, $expChallenge + $expTugas);
|
||
}
|
||
|
||
/**
|
||
* Ambil data leaderboard dengan EXP yang dihitung dinamis.
|
||
*/
|
||
private function getData(): array
|
||
{
|
||
$siswa = Auth::guard('siswa')->user();
|
||
|
||
$now = Carbon::now();
|
||
$semester = $now->month >= 7 ? '1' : '2';
|
||
$tahunAjaran = $now->month >= 7
|
||
? $now->year . '/' . ($now->year + 1)
|
||
: ($now->year - 1) . '/' . $now->year;
|
||
|
||
// Ambil semua siswa di kelas yang sama
|
||
$semuaSiswa = Siswa::where('id_kelas', $siswa->id_kelas)->get();
|
||
|
||
// Hitung EXP tiap siswa secara dinamis
|
||
$leaderboard = $semuaSiswa->map(function ($s) {
|
||
return [
|
||
'id_siswa' => $s->id_siswa,
|
||
'nama' => $s->nama,
|
||
'nisn' => $s->nisn,
|
||
'foto_profil' => $s->foto_profil,
|
||
'foto_url' => $s->foto_profil ? Storage::url($s->foto_profil) : null,
|
||
'exp' => $this->hitungExpSiswa($s->id_siswa, $s->id_kelas),
|
||
];
|
||
})
|
||
->sortByDesc('exp')
|
||
->values()
|
||
->map(function ($item, $i) {
|
||
$item['ranking'] = $i + 1;
|
||
return $item;
|
||
});
|
||
|
||
// Sinkronisasi ke tabel leaderboards agar badge bisa dicek
|
||
foreach ($leaderboard as $item) {
|
||
Leaderboard::updateOrCreate(
|
||
[
|
||
'id_siswa' => $item['id_siswa'],
|
||
'id_kelas' => $siswa->id_kelas,
|
||
'semester' => $semester,
|
||
'tahun_ajaran' => $tahunAjaran,
|
||
],
|
||
[
|
||
'total_exp' => $item['exp'],
|
||
'ranking' => $item['ranking'],
|
||
]
|
||
);
|
||
}
|
||
|
||
$myRank = $leaderboard->firstWhere('id_siswa', $siswa->id_siswa);
|
||
|
||
return compact('leaderboard', 'myRank', 'semester', 'tahunAjaran', 'siswa');
|
||
}
|
||
|
||
public function index()
|
||
{
|
||
$data = $this->getData();
|
||
unset($data['siswa']);
|
||
return view('siswa.leaderboard.index', $data);
|
||
}
|
||
|
||
/**
|
||
* Endpoint JSON untuk polling real-time.
|
||
*/
|
||
public function json()
|
||
{
|
||
$data = $this->getData();
|
||
$siswa = $data['siswa'];
|
||
|
||
// Evaluasi badge leaderboard berdasarkan data terbaru
|
||
app(BadgeService::class)->checkLeaderboardBadges(
|
||
$siswa->id_siswa,
|
||
$siswa->id_kelas
|
||
);
|
||
|
||
$badgeSiswa = SiswaBadge::with('badge')
|
||
->where('id_siswa', $siswa->id_siswa)
|
||
->whereHas('badge', fn($q) => $q->whereIn('syarat', ['leaderboard_top5', 'leaderboard_top1']))
|
||
->get()
|
||
->map(fn($sb) => [
|
||
'id_badge' => $sb->id_badge,
|
||
'nama_badge' => $sb->badge->nama_badge,
|
||
'deskripsi' => $sb->badge->deskripsi,
|
||
'icon_url' => asset($sb->badge->icon_badge),
|
||
]);
|
||
|
||
unset($data['siswa']);
|
||
$data['badgeSiswa'] = $badgeSiswa;
|
||
|
||
return response()->json($data);
|
||
}
|
||
} |