MIF_E31230356/app/Http/Controllers/Siswa/LeaderboardController.php

191 lines
6.7 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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 23 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);
}
}