badge
This commit is contained in:
parent
d06fafb7cc
commit
f84402aff7
|
|
@ -6,6 +6,7 @@
|
|||
use App\Models\Challenge;
|
||||
use App\Models\PesertaChallenge;
|
||||
use App\Models\Leaderboard;
|
||||
use App\Services\BadgeService;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Carbon\Carbon;
|
||||
|
|
@ -101,7 +102,6 @@ public function submit(Request $request, $id_challenge)
|
|||
}
|
||||
}
|
||||
|
||||
// Semester & tahun ajaran otomatis
|
||||
$now = Carbon::now();
|
||||
$semester = $now->month >= 7 ? '1' : '2';
|
||||
$tahunAjaran = $now->month >= 7
|
||||
|
|
@ -118,7 +118,6 @@ public function submit(Request $request, $id_challenge)
|
|||
'status' => 'selesai',
|
||||
]);
|
||||
|
||||
// firstOrCreate dengan semester & tahun_ajaran sebagai key
|
||||
$lb = Leaderboard::firstOrCreate(
|
||||
[
|
||||
'id_siswa' => $siswa->id_siswa,
|
||||
|
|
@ -131,7 +130,6 @@ public function submit(Request $request, $id_challenge)
|
|||
|
||||
$lb->increment('total_exp', $totalExp);
|
||||
|
||||
// Recalculate ranking per kelas + semester + tahun ajaran
|
||||
Leaderboard::where('id_kelas', $siswa->id_kelas)
|
||||
->where('semester', $semester)
|
||||
->where('tahun_ajaran', $tahunAjaran)
|
||||
|
|
@ -140,6 +138,9 @@ public function submit(Request $request, $id_challenge)
|
|||
->each(fn($row, $i) => $row->update(['ranking' => $i + 1]));
|
||||
});
|
||||
|
||||
// --- Cek & berikan badge challenge (di luar transaksi agar tidak block) ---
|
||||
app(BadgeService::class)->checkChallengeBadges($siswa->id_siswa);
|
||||
|
||||
return redirect()->route('siswa.challenge.hasil', $id_challenge);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Leaderboard;
|
||||
use App\Services\BadgeService;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Carbon\Carbon;
|
||||
|
|
@ -41,19 +42,34 @@ private function getData()
|
|||
|
||||
$myRank = $leaderboard->firstWhere('id_siswa', $siswa->id_siswa);
|
||||
|
||||
return compact('leaderboard', 'myRank', 'semester', 'tahunAjaran');
|
||||
return compact('leaderboard', 'myRank', 'semester', 'tahunAjaran', 'siswa');
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
$data = $this->getData();
|
||||
// Keluarkan 'siswa' dari data yang dikirim ke view (tidak dibutuhkan di view)
|
||||
unset($data['siswa']);
|
||||
return view('siswa.leaderboard.index', $data);
|
||||
}
|
||||
|
||||
// Endpoint JSON untuk polling real-time
|
||||
/**
|
||||
* Endpoint JSON untuk polling real-time.
|
||||
* Setiap kali dipanggil, badge leaderboard siswa yang sedang login
|
||||
* dievaluasi ulang (diberikan atau dicabut sesuai ranking saat ini).
|
||||
*/
|
||||
public function json()
|
||||
{
|
||||
$data = $this->getData();
|
||||
$data = $this->getData();
|
||||
$siswa = $data['siswa'];
|
||||
|
||||
// Evaluasi badge leaderboard secara real-time untuk siswa yang sedang login
|
||||
app(BadgeService::class)->checkLeaderboardBadges(
|
||||
$siswa->id_siswa,
|
||||
$siswa->id_kelas
|
||||
);
|
||||
|
||||
unset($data['siswa']);
|
||||
return response()->json($data);
|
||||
}
|
||||
}
|
||||
|
|
@ -9,6 +9,7 @@
|
|||
use App\Models\Tugas;
|
||||
use App\Models\PengumpulanTugas;
|
||||
use App\Models\Mengajar;
|
||||
use App\Services\BadgeService;
|
||||
|
||||
class TugasController extends Controller
|
||||
{
|
||||
|
|
@ -24,7 +25,6 @@ public function index()
|
|||
{
|
||||
$siswa = Auth::guard('siswa')->user();
|
||||
|
||||
// Ambil semua tugas untuk kelas siswa
|
||||
$semuaTugas = Tugas::with(['mengajar.mapel', 'mengajar.guru'])
|
||||
->whereHas('mengajar', function ($q) use ($siswa) {
|
||||
$q->where('id_kelas', $siswa->id_kelas);
|
||||
|
|
@ -32,39 +32,37 @@ public function index()
|
|||
->orderBy('deadline', 'asc')
|
||||
->get();
|
||||
|
||||
// Tandai status tiap tugas untuk siswa ini
|
||||
$tugasList = $semuaTugas->map(function ($tugas) use ($siswa) {
|
||||
$pengumpulan = PengumpulanTugas::where('id_tugas', $tugas->id_tugas)
|
||||
->where('id_siswa', $siswa->id_siswa)
|
||||
->first();
|
||||
|
||||
$now = Carbon::now();
|
||||
$now = Carbon::now();
|
||||
$deadline = Carbon::parse($tugas->deadline);
|
||||
|
||||
if ($pengumpulan) {
|
||||
$status = $pengumpulan->status; // 'dikumpulkan' atau 'terlambat'
|
||||
$status = $pengumpulan->status;
|
||||
} else {
|
||||
$status = $now->greaterThan($deadline) ? 'terlambat' : 'belum';
|
||||
}
|
||||
|
||||
return [
|
||||
'id_tugas' => $tugas->id_tugas,
|
||||
'judul' => $tugas->judul_tugas,
|
||||
'keterangan' => $tugas->keterangan,
|
||||
'deadline' => $deadline,
|
||||
'nama_mapel' => optional(optional($tugas->mengajar)->mapel)->nama_mapel ?? '-',
|
||||
'nama_guru' => optional(optional($tugas->mengajar)->guru)->nama ?? '-',
|
||||
'status' => $status,
|
||||
'sudah_kumpul'=> !is_null($pengumpulan),
|
||||
'lampiran' => $pengumpulan?->lampiran_tugas,
|
||||
'exp' => $pengumpulan?->exp ?? 0,
|
||||
'id_tugas' => $tugas->id_tugas,
|
||||
'judul' => $tugas->judul_tugas,
|
||||
'keterangan' => $tugas->keterangan,
|
||||
'deadline' => $deadline,
|
||||
'nama_mapel' => optional(optional($tugas->mengajar)->mapel)->nama_mapel ?? '-',
|
||||
'nama_guru' => optional(optional($tugas->mengajar)->guru)->nama ?? '-',
|
||||
'status' => $status,
|
||||
'sudah_kumpul' => !is_null($pengumpulan),
|
||||
'lampiran' => $pengumpulan?->lampiran_tugas,
|
||||
'exp' => $pengumpulan?->exp ?? 0,
|
||||
];
|
||||
});
|
||||
|
||||
// Kelompokkan: belum & pending vs sudah dikumpulkan
|
||||
$tugasBelum = $tugasList->filter(fn($t) => !$t['sudah_kumpul'] && $t['status'] !== 'terlambat');
|
||||
$tugasBelum = $tugasList->filter(fn($t) => !$t['sudah_kumpul'] && $t['status'] !== 'terlambat');
|
||||
$tugasTerlambat = $tugasList->filter(fn($t) => !$t['sudah_kumpul'] && $t['status'] === 'terlambat');
|
||||
$tugasSelesai = $tugasList->filter(fn($t) => $t['sudah_kumpul']);
|
||||
$tugasSelesai = $tugasList->filter(fn($t) => $t['sudah_kumpul']);
|
||||
|
||||
return view('siswa.tugas.index', compact('tugasBelum', 'tugasTerlambat', 'tugasSelesai'));
|
||||
}
|
||||
|
|
@ -108,14 +106,12 @@ public function submit(Request $request, $id_tugas)
|
|||
'lampiran_tugas.max' => 'Ukuran file maksimal 5MB.',
|
||||
]);
|
||||
|
||||
// Cek tugas valid untuk kelas siswa
|
||||
$tugas = Tugas::whereHas('mengajar', function ($q) use ($siswa) {
|
||||
$q->where('id_kelas', $siswa->id_kelas);
|
||||
})
|
||||
->where('id_tugas', $id_tugas)
|
||||
->firstOrFail();
|
||||
|
||||
// Cek sudah dikumpulkan belum
|
||||
$sudahAda = PengumpulanTugas::where('id_tugas', $id_tugas)
|
||||
->where('id_siswa', $siswa->id_siswa)
|
||||
->exists();
|
||||
|
|
@ -124,12 +120,10 @@ public function submit(Request $request, $id_tugas)
|
|||
return back()->with('error', 'Kamu sudah mengumpulkan tugas ini.');
|
||||
}
|
||||
|
||||
// Upload file
|
||||
$file = $request->file('lampiran_tugas');
|
||||
$filename = 'tugas_' . $siswa->id_siswa . '_' . $id_tugas . '_' . time() . '.' . $file->getClientOriginalExtension();
|
||||
$path = $file->storeAs('pengumpulan_tugas', $filename, 'public');
|
||||
|
||||
// Tentukan status: terlambat atau dikumpulkan
|
||||
$now = Carbon::now();
|
||||
$deadline = Carbon::parse($tugas->deadline);
|
||||
$status = $now->greaterThan($deadline) ? 'terlambat' : 'dikumpulkan';
|
||||
|
|
@ -139,10 +133,15 @@ public function submit(Request $request, $id_tugas)
|
|||
'id_siswa' => $siswa->id_siswa,
|
||||
'lampiran_tugas' => $path,
|
||||
'tanggal_submit' => $now,
|
||||
'exp' => 0, // exp diberikan guru saat menilai
|
||||
'exp' => 0,
|
||||
'status' => $status,
|
||||
]);
|
||||
|
||||
// --- Cek & berikan badge tugas hanya jika tepat waktu ---
|
||||
if ($status === 'dikumpulkan') {
|
||||
app(BadgeService::class)->checkTugasBadges($siswa->id_siswa);
|
||||
}
|
||||
|
||||
$pesan = $status === 'terlambat'
|
||||
? 'Tugas berhasil dikumpulkan (terlambat).'
|
||||
: 'Tugas berhasil dikumpulkan! 🎉';
|
||||
|
|
|
|||
|
|
@ -0,0 +1,168 @@
|
|||
<?php
|
||||
|
||||
namespace App\Services;
|
||||
|
||||
use App\Models\Badge;
|
||||
use App\Models\SiswaBadge;
|
||||
use App\Models\PesertaChallenge;
|
||||
use App\Models\PengumpulanTugas;
|
||||
use App\Models\Leaderboard;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class BadgeService
|
||||
{
|
||||
/**
|
||||
* Syarat (kolom `syarat` di tabel badges) yang dikenali sistem.
|
||||
* Nilai ini harus sama persis dengan isi kolom `syarat` di DB.
|
||||
*
|
||||
* CHALLENGE
|
||||
* challenge_1 → selesaikan 1 challenge
|
||||
* challenge_3 → selesaikan 3 challenge
|
||||
*
|
||||
* TUGAS
|
||||
* tugas_1 → kumpulkan 1 tugas (tepat waktu)
|
||||
* tugas_3 → kumpulkan 3 tugas (tepat waktu)
|
||||
*
|
||||
* LEADERBOARD (dicek & dicabut secara real-time)
|
||||
* leaderboard_top5 → masuk top 5 leaderboard kelas aktif
|
||||
* leaderboard_top1 → raih peringkat 1 leaderboard kelas aktif
|
||||
*/
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// ENTRY POINTS
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Dipanggil setelah siswa submit challenge.
|
||||
* Mengecek & memberikan badge challenge.
|
||||
*/
|
||||
public function checkChallengeBadges(int $idSiswa): void
|
||||
{
|
||||
$jumlahSelesai = PesertaChallenge::where('id_siswa', $idSiswa)
|
||||
->where('status', 'selesai')
|
||||
->count();
|
||||
|
||||
$this->grantIfEligible($idSiswa, 'challenge_1', $jumlahSelesai >= 1);
|
||||
$this->grantIfEligible($idSiswa, 'challenge_3', $jumlahSelesai >= 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dipanggil setelah siswa submit tugas.
|
||||
* Mengecek & memberikan badge tugas (hanya status 'dikumpulkan' / tepat waktu).
|
||||
*/
|
||||
public function checkTugasBadges(int $idSiswa): void
|
||||
{
|
||||
$jumlahKumpul = PengumpulanTugas::where('id_siswa', $idSiswa)
|
||||
->where('status', 'dikumpulkan')
|
||||
->count();
|
||||
|
||||
$this->grantIfEligible($idSiswa, 'tugas_1', $jumlahKumpul >= 1);
|
||||
$this->grantIfEligible($idSiswa, 'tugas_3', $jumlahKumpul >= 3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Dipanggil dari endpoint JSON leaderboard (polling real-time).
|
||||
* Badge leaderboard DICABUT jika siswa tidak lagi memenuhi syarat,
|
||||
* dan DIBERIKAN KEMBALI jika kembali memenuhi syarat.
|
||||
*
|
||||
* Hanya leaderboard semester & tahun ajaran aktif yang dievaluasi.
|
||||
*/
|
||||
public function checkLeaderboardBadges(int $idSiswa, int $idKelas): void
|
||||
{
|
||||
[$semester, $tahunAjaran] = $this->semesterAktif();
|
||||
|
||||
$lb = Leaderboard::where('id_siswa', $idSiswa)
|
||||
->where('id_kelas', $idKelas)
|
||||
->where('semester', $semester)
|
||||
->where('tahun_ajaran', $tahunAjaran)
|
||||
->first();
|
||||
|
||||
$ranking = $lb?->ranking ?? 0;
|
||||
|
||||
// top5: ranking 1-5, top1: hanya ranking 1
|
||||
$isTop5 = $ranking >= 1 && $ranking <= 5;
|
||||
$isTop1 = $ranking === 1;
|
||||
|
||||
// Untuk badge leaderboard: grant jika eligible, revoke jika tidak
|
||||
$this->grantOrRevoke($idSiswa, 'leaderboard_top5', $isTop5);
|
||||
$this->grantOrRevoke($idSiswa, 'leaderboard_top1', $isTop1);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// PRIVATE HELPERS
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Berikan badge jika eligible. Tidak melakukan apa-apa jika sudah punya.
|
||||
* Digunakan untuk badge challenge & tugas (tidak pernah dicabut).
|
||||
*/
|
||||
private function grantIfEligible(int $idSiswa, string $syarat, bool $eligible): void
|
||||
{
|
||||
if (!$eligible) {
|
||||
return;
|
||||
}
|
||||
|
||||
$badge = Badge::where('syarat', $syarat)->first();
|
||||
if (!$badge) {
|
||||
return; // badge belum di-seed di DB, skip
|
||||
}
|
||||
|
||||
// Idempoten: cek dulu sebelum insert
|
||||
$sudahPunya = SiswaBadge::where('id_siswa', $idSiswa)
|
||||
->where('id_badge', $badge->id_badge)
|
||||
->exists();
|
||||
|
||||
if (!$sudahPunya) {
|
||||
SiswaBadge::create([
|
||||
'id_siswa' => $idSiswa,
|
||||
'id_badge' => $badge->id_badge,
|
||||
'tanggal_diberikan' => Carbon::now(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Berikan badge jika eligible, CABUT jika tidak.
|
||||
* Digunakan khusus untuk badge leaderboard (real-time).
|
||||
*/
|
||||
private function grantOrRevoke(int $idSiswa, string $syarat, bool $eligible): void
|
||||
{
|
||||
$badge = Badge::where('syarat', $syarat)->first();
|
||||
if (!$badge) {
|
||||
return;
|
||||
}
|
||||
|
||||
$record = SiswaBadge::where('id_siswa', $idSiswa)
|
||||
->where('id_badge', $badge->id_badge)
|
||||
->first();
|
||||
|
||||
if ($eligible && !$record) {
|
||||
// Berikan badge
|
||||
SiswaBadge::create([
|
||||
'id_siswa' => $idSiswa,
|
||||
'id_badge' => $badge->id_badge,
|
||||
'tanggal_diberikan' => Carbon::now(),
|
||||
]);
|
||||
} elseif (!$eligible && $record) {
|
||||
// Cabut badge
|
||||
$record->delete();
|
||||
}
|
||||
// Jika sudah punya & masih eligible, atau tidak punya & tidak eligible → tidak perlu apa-apa
|
||||
}
|
||||
|
||||
/**
|
||||
* Mengembalikan [semester, tahun_ajaran] berdasarkan tanggal sekarang,
|
||||
* konsisten dengan logika di ChallengeController.
|
||||
*/
|
||||
private function semesterAktif(): array
|
||||
{
|
||||
$now = Carbon::now();
|
||||
$semester = $now->month >= 7 ? '1' : '2';
|
||||
$tahunAjaran = $now->month >= 7
|
||||
? $now->year . '/' . ($now->year + 1)
|
||||
: ($now->year - 1) . '/' . $now->year;
|
||||
|
||||
return [$semester, $tahunAjaran];
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 126 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 137 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 159 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 140 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 107 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 113 KiB |
Loading…
Reference in New Issue