504 lines
22 KiB
PHP
504 lines
22 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\RoleGuru;
|
|
|
|
use App\Exports\QuizExport;
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\Kelas;
|
|
use App\Models\MataPelajaran;
|
|
use App\Models\QuizLevelSetting;
|
|
use App\Models\QuizQuestions;
|
|
use App\Models\Quizzes;
|
|
use App\Models\Siswa;
|
|
use App\Models\TahunAjaran;
|
|
use App\Notifications\QuizBaruNotification;
|
|
use App\Repositories\QuizRepository;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\Session;
|
|
use Maatwebsite\Excel\Concerns\FromView;
|
|
use Maatwebsite\Excel\Facades\Excel;
|
|
use RealRashid\SweetAlert\Facades\Alert;
|
|
|
|
class QuizController extends Controller
|
|
{
|
|
protected $param;
|
|
|
|
public function __construct(QuizRepository $quiz)
|
|
{
|
|
$this->param = $quiz;
|
|
}
|
|
|
|
public function index(Request $request)
|
|
{
|
|
$nip = Auth::user()->guru->nip;
|
|
$matpel = MataPelajaran::where('guru_nip', $nip)->get();
|
|
$kelas = Kelas::all();
|
|
$tahunAjaran = TahunAjaran::where('status', 'aktif')->get();
|
|
|
|
// Get quizzes created by this teacher
|
|
$quiz = Quizzes::with(['mataPelajaran', 'quizLevelSetting'])
|
|
->whereHas('mataPelajaran', function ($q) use ($nip) {
|
|
$q->where('guru_nip', $nip);
|
|
})
|
|
->orderBy('created_at', 'desc')
|
|
->get();
|
|
|
|
return view("pages.role_guru.quiz.index", compact(['matpel', 'kelas', 'tahunAjaran', 'quiz']));
|
|
}
|
|
|
|
public function getQuizByMatpel($id)
|
|
{
|
|
$nip = Auth::user()->guru->nip;
|
|
$quizzes = Quizzes::where('matapelajaran_id', $id)
|
|
->whereHas('mataPelajaran', function ($q) use ($nip) {
|
|
$q->where('guru_nip', $nip);
|
|
})
|
|
->get();
|
|
|
|
return response()->json($quizzes);
|
|
}
|
|
|
|
|
|
public function excelDownload()
|
|
{
|
|
return Excel::download(new QuizExport, "format_excel_untuk_quiz.xlsx");
|
|
}
|
|
|
|
public function preview(Request $request)
|
|
{
|
|
$request->validate([
|
|
'file' => 'required|mimes:xlsx,xls'
|
|
]);
|
|
|
|
$data = Excel::toArray([], $request->file('file'));
|
|
|
|
// Ambil sheet pertama
|
|
$rows = $data[0];
|
|
|
|
$filteredRows = [];
|
|
$jumlahSoalPerLevel = [];
|
|
$totalSoalPeLevel = [];
|
|
$batasNaikLevel = [];
|
|
$skorLevel = [];
|
|
|
|
foreach ($rows as $index => $row) {
|
|
if ($index == 0 || !empty($row[1])) {
|
|
$filteredRows[] = $row;
|
|
|
|
$level = $row[3];
|
|
$skor = $row[8];
|
|
if (!empty($level) && $index != 0) {
|
|
$key = 'level' . $level;
|
|
if (!isset($jumlahSoalPerLevel[$key])) {
|
|
$jumlahSoalPerLevel[$key] = 0;
|
|
$totalSoalPeLevel[$key] = 0;
|
|
}
|
|
$jumlahSoalPerLevel[$key]++;
|
|
$totalSoalPeLevel[$key]++;
|
|
$skorLevel[$key] = $skor;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Hitung batas naik level = 50% dari jumlah soal di level tersebut
|
|
foreach ($jumlahSoalPerLevel as $key => $jumlah) {
|
|
// Ambil level dari key, contoh: 'level2' → 2
|
|
$level = str_replace('level', '', $key);
|
|
$keyFase = 'fase' . $level;
|
|
|
|
// Hitung 50% lalu dibulatkan ke atas (ceil)
|
|
$batasNaikLevel[$keyFase] = (int) ceil($jumlah * 0.5);
|
|
|
|
$jumlahSoalPerLevel[$key] = (int) ceil($jumlah * 0.5);
|
|
}
|
|
|
|
|
|
$soalCount = count($filteredRows) - 1;
|
|
|
|
// Simpan preview dan jumlah soal ke session
|
|
Session::put('judul', $request->judul);
|
|
Session::put('deskripsi', $request->deskripsi);
|
|
Session::put('matapelajaran_id', $request->matapelajaran_id);
|
|
Session::put('waktu', $request->waktu);
|
|
Session::put('preview_soal', $filteredRows);
|
|
Session::put('total_soal', $soalCount);
|
|
Session::put('total_soal_tampil', $request->total_soal_tampil ?? 20);
|
|
Session::put('uploaded_filename', $request->file('file')->getClientOriginalName());
|
|
|
|
// quiz level settings
|
|
Session::put('total_soal_per_level', $totalSoalPeLevel);
|
|
Session::put('jumlah_soal_per_level', $jumlahSoalPerLevel);
|
|
Session::put('level_awal', $request->level_awal);
|
|
Session::put('batas_naik_level', $batasNaikLevel);
|
|
Session::put('skor_level', $skorLevel);
|
|
Session::put('kkm', $request->kkm);
|
|
|
|
return redirect()->back();
|
|
}
|
|
|
|
protected function removeSession()
|
|
{
|
|
session()->forget('judul');
|
|
session()->forget('deskripsi');
|
|
session()->forget('matapelajaran_id');
|
|
session()->forget('waktu');
|
|
session()->forget('preview_soal');
|
|
session()->forget('total_soal');
|
|
session()->forget('total_soal_tampil');
|
|
session()->forget('uploaded_filename');
|
|
|
|
// quiz level settings
|
|
session()->forget('total_soal_per_level');
|
|
session()->forget('jumlah_soal_per_level');
|
|
session()->forget('level_awal');
|
|
session()->forget('batas_naik_level');
|
|
session()->forget('skor_level');
|
|
session()->forget('kkm');
|
|
}
|
|
|
|
public function resetPreview()
|
|
{
|
|
$this->removeSession();
|
|
|
|
return redirect()->back()->with('success', 'Data preview berhasil direset.');
|
|
}
|
|
|
|
/**
|
|
* Show the form for creating a new resource.
|
|
*/
|
|
public function create()
|
|
{
|
|
$nip = Auth::user()->guru->nip;
|
|
$matpel = MataPelajaran::where("guru_nip", $nip)->get();
|
|
$naik_level = json_encode(session('batas_naik_level') ?? []);
|
|
return view("pages.role_guru.quiz.create", compact(['matpel', 'naik_level']));
|
|
}
|
|
|
|
/**
|
|
* Store a newly created resource in storage.
|
|
*/
|
|
public function store(Request $request)
|
|
{
|
|
$preview = session('preview_soal');
|
|
if (!is_array($preview)) {
|
|
return redirect()->back()->with('validation_error', [
|
|
'title' => 'Error',
|
|
'message' => 'Data soal tidak ditemukan atau session sudah habis. Silakan ulangi proses import/preview quiz.'
|
|
]);
|
|
}
|
|
array_shift($preview);
|
|
|
|
if (!$preview || count($preview) <= 1) {
|
|
\Log::error('Quiz Import Error: Tidak ada data untuk disimpan');
|
|
Alert::error("Terjadi Kesalahan", "Tidak ada data untuk disimpan.");
|
|
return redirect()->back();
|
|
}
|
|
|
|
if ($request->total_soal_tampil < 10) {
|
|
\Log::error('Quiz Import Error: Jumlah soal minimal 10');
|
|
Alert::error("Terjadi Kesalahan", "Jumlah soal minimal 10.");
|
|
return redirect()->back();
|
|
}
|
|
|
|
// ========================================
|
|
// VALIDASI KETAT UNTUK MENCEGAH BUG QUIZ
|
|
// ========================================
|
|
|
|
// 1. VALIDASI: Total soal per level harus sama dengan total soal tampil
|
|
$totalSoalPerLevel = 0;
|
|
foreach ($request->jumlah_soal_per_level as $key => $value) {
|
|
$totalSoalPerLevel += (int) $value;
|
|
}
|
|
|
|
if ($totalSoalPerLevel != $request->total_soal_tampil) {
|
|
\Log::error('Quiz Import Error: Total soal per level tidak sama dengan total soal tampil');
|
|
\Log::error('Detail: Total soal per level = ' . $totalSoalPerLevel . ', Total soal tampil = ' . $request->total_soal_tampil);
|
|
\Log::error('Detail per level: ' . json_encode($request->jumlah_soal_per_level));
|
|
|
|
return redirect()->back()->with('validation_error', [
|
|
'title' => 'Error Validasi',
|
|
'message' => "Total soal per level ($totalSoalPerLevel) harus sama dengan total soal tampil ({$request->total_soal_tampil}).<br><br><strong>POTENSI BUG:</strong> Jika tidak sama, siswa bisa stuck di level tertentu karena soal tidak cukup.<br><strong>CATATAN:</strong> Pastikan jumlah soal per level dijumlahkan sama dengan total soal tampil."
|
|
]);
|
|
}
|
|
|
|
// 2. VALIDASI: Hitung jumlah soal yang tersedia per level dari data import
|
|
$levelCounts = [];
|
|
foreach ($preview as $row) {
|
|
if (!empty($row[3])) { // level
|
|
$level = $row[3];
|
|
$levelCounts[$level] = ($levelCounts[$level] ?? 0) + 1;
|
|
}
|
|
}
|
|
|
|
// 3. VALIDASI: Jumlah soal per level tidak boleh melebihi soal yang tersedia
|
|
foreach ($request->jumlah_soal_per_level as $key => $value) {
|
|
$level = str_replace('level', '', $key);
|
|
$availableInLevel = $levelCounts[$level] ?? 0;
|
|
|
|
if ($value > $availableInLevel) {
|
|
\Log::error('Quiz Import Error: Jumlah soal setting melebihi soal yang tersedia');
|
|
\Log::error('Detail: Level ' . $level . ' - Setting: ' . $value . ', Tersedia: ' . $availableInLevel);
|
|
|
|
return redirect()->back()->with('validation_error', [
|
|
'title' => 'Error Validasi',
|
|
'message' => "Level $level: Setting $value soal, tapi hanya ada $availableInLevel soal tersedia.<br><br><strong>POTENSI BUG:</strong> Sistem akan stuck karena tidak ada cukup soal di level tersebut.<br><strong>CATATAN:</strong> Kurangi jumlah soal setting atau tambah soal di level tersebut."
|
|
]);
|
|
}
|
|
}
|
|
|
|
// 4. VALIDASI: Batas naik level tidak boleh melebihi jumlah soal di level tersebut
|
|
foreach ($request->batas_naik_level as $key => $value) {
|
|
$level = str_replace('fase', '', $key);
|
|
$soalInLevel = $request->jumlah_soal_per_level["level$level"] ?? 0;
|
|
|
|
// VALIDASI BARU: Batas naik level tidak boleh sama atau lebih besar dari jumlah soal di level
|
|
if ($value >= $soalInLevel) {
|
|
return redirect()->back()->with('validation_error', [
|
|
'title' => 'Error Validasi',
|
|
'message' => "Level $level: Syarat naik level ($value) tidak boleh sama atau lebih besar dari jumlah soal ($soalInLevel).<br><br><strong>POTENSI BUG:</strong> Jika siswa salah satu saja, quiz akan stuck di level ini.<br><strong>CATATAN:</strong> Kurangi syarat naik level atau tambah jumlah soal di level ini."
|
|
]);
|
|
}
|
|
|
|
if ($value > $soalInLevel) {
|
|
\Log::error('Quiz Import Error: Batas naik level melebihi jumlah soal di level');
|
|
\Log::error('Detail: Level ' . $level . ' - Batas naik: ' . $value . ', Soal di level: ' . $soalInLevel);
|
|
return redirect()->back()->with('validation_error', [
|
|
'title' => 'Error Validasi',
|
|
'message' => "Level $level: Batas naik level ($value) tidak boleh melebihi jumlah soal ($soalInLevel).<br><br><strong>POTENSI BUG:</strong> Siswa tidak akan pernah naik level karena batas terlalu tinggi.<br><strong>CATATAN:</strong> Batas naik level harus ≤ jumlah soal di level tersebut."
|
|
]);
|
|
}
|
|
}
|
|
|
|
// 5. VALIDASI: Pastikan ada soal di setiap level yang di-setting
|
|
foreach ($request->jumlah_soal_per_level as $key => $value) {
|
|
$level = str_replace('level', '', $key);
|
|
$availableInLevel = $levelCounts[$level] ?? 0;
|
|
|
|
if ($availableInLevel == 0) {
|
|
\Log::error('Quiz Import Error: Tidak ada soal di level yang di-setting');
|
|
\Log::error('Detail: Level ' . $level . ' tidak memiliki soal di data import');
|
|
|
|
return redirect()->back()->with('validation_error', [
|
|
'title' => 'Error Validasi',
|
|
'message' => "Level $level: Tidak ada soal tersedia di level ini.<br><br><strong>POTENSI BUG:</strong> Sistem akan stuck karena tidak ada soal di level tersebut.<br><strong>CATATAN:</strong> Pastikan data import memiliki soal dengan level $level atau hapus setting level ini."
|
|
]);
|
|
}
|
|
}
|
|
|
|
// 6. VALIDASI: Level harus berurutan (1, 2, 3, dst)
|
|
$levels = array_keys($request->jumlah_soal_per_level);
|
|
sort($levels);
|
|
$expectedLevels = [];
|
|
for ($i = 1; $i <= count($levels); $i++) {
|
|
$expectedLevels[] = "level$i";
|
|
}
|
|
|
|
if ($levels !== $expectedLevels) {
|
|
\Log::error('Quiz Import Error: Level tidak berurutan');
|
|
\Log::error('Detail: Level yang ada = ' . json_encode($levels) . ', Level yang diharapkan = ' . json_encode($expectedLevels));
|
|
|
|
return redirect()->back()->with('validation_error', [
|
|
'title' => 'Error Validasi',
|
|
'message' => "Level harus berurutan (Level 1, Level 2, Level 3, dst).<br><br><strong>POTENSI BUG:</strong> Sistem adaptive learning akan bingung dengan level yang tidak berurutan.<br><strong>CATATAN:</strong> Pastikan level di data import berurutan dari 1, 2, 3, dst."
|
|
]);
|
|
}
|
|
|
|
// Update session dengan nilai total_soal_tampil yang baru
|
|
Session::put('total_soal_tampil', $request->total_soal_tampil);
|
|
|
|
// VALIDASI: Jumlah soal yang harus dikerjakan per level tidak boleh melebihi jumlah soal di bank soal
|
|
$totalSoalPerLevel = 0;
|
|
foreach ($request->jumlah_soal_per_level as $key => $value) {
|
|
$level = str_replace('level', '', $key);
|
|
$soalTersedia = $request->total_soal_per_level["level$level"] ?? 0;
|
|
if ($value > $soalTersedia) {
|
|
return redirect()->back()->with('validation_error', [
|
|
'title' => 'Error Validasi',
|
|
'message' => "Level $level: Jumlah soal yang dikerjakan ($value) tidak boleh lebih besar dari jumlah soal di bank soal ($soalTersedia)."
|
|
]);
|
|
}
|
|
$totalSoalPerLevel += (int) $value;
|
|
}
|
|
// VALIDASI: Total soal yang harus dikerjakan (semua level) harus sama dengan total soal tampil
|
|
if ($totalSoalPerLevel != $request->total_soal_tampil) {
|
|
return redirect()->back()->with('validation_error', [
|
|
'title' => 'Error Validasi',
|
|
'message' => "Total soal yang harus dikerjakan ($totalSoalPerLevel) tidak sama dengan total soal tampil ({$request->total_soal_tampil}).<br><br><strong>POTENSI BUG:</strong> Quiz bisa stuck atau soal tidak cukup.<br><strong>CATATAN:</strong> Sesuaikan jumlah soal per level agar totalnya sama dengan total soal tampil."
|
|
]);
|
|
}
|
|
|
|
// VALIDASI: Cegah quiz stuck jika syarat naik level tidak tercapai dan soal habis
|
|
foreach ($request->batas_naik_level as $key => $value) {
|
|
$level = str_replace('fase', '', $key);
|
|
$jumlah_soal = $request->jumlah_soal_per_level["level$level"] ?? 0;
|
|
$batas_naik = $value;
|
|
// Jika syarat naik lebih besar dari jumlah soal, mustahil
|
|
if ($batas_naik > $jumlah_soal) {
|
|
return redirect()->back()->with('validation_error', [
|
|
'title' => 'Error Validasi',
|
|
'message' => "Level $level: Syarat naik level ($batas_naik) tidak boleh lebih besar dari jumlah soal yang dikerjakan ($jumlah_soal)."
|
|
]);
|
|
}
|
|
// Jika syarat naik level terlalu rendah, tetap valid, tapi cek kemungkinan stuck
|
|
// Simulasi: Jika siswa menjawab semua soal tapi benar kurang dari syarat naik, quiz stuck
|
|
// Contoh: syarat naik 3 dari 10, jika siswa hanya benar 2, quiz stuck
|
|
// Validasi: syarat naik level harus bisa dicapai dengan minimal 1 benar di setiap soal
|
|
// (tidak perlu, karena sudah dicegah oleh logika di atas)
|
|
// Namun, jika syarat naik terlalu rendah, warning saja (tidak error)
|
|
// Jika syarat naik terlalu tinggi, error
|
|
// Jika syarat naik level tidak tercapai dan soal habis, quiz stuck
|
|
// (Sudah dicegah oleh validasi di atas)
|
|
}
|
|
|
|
// VALIDASI: Cegah quiz stuck jika siswa gagal naik level dan soal habis
|
|
/*
|
|
$levelKeys = array_keys($request->jumlah_soal_per_level);
|
|
$levelCount = count($levelKeys);
|
|
for ($i = 0; $i < $levelCount - 1; $i++) { // Kecuali level terakhir
|
|
$currentLevelKey = $levelKeys[$i];
|
|
$nextLevelKeys = array_slice($levelKeys, $i + 1);
|
|
$soalDiLevelIni = (int) $request->jumlah_soal_per_level[$currentLevelKey];
|
|
$soalDiLevelBerikutnya = 0;
|
|
foreach ($nextLevelKeys as $k) {
|
|
$soalDiLevelBerikutnya += (int) $request->jumlah_soal_per_level[$k];
|
|
}
|
|
$totalSoalTampil = (int) $request->total_soal_tampil;
|
|
$batasNaik = (int) ($request->batas_naik_level['fase' . ($i + 1)] ?? 0);
|
|
// Jika semua soal di level ini habis, dan siswa gagal naik (benar < batas naik), quiz stuck
|
|
// Kondisi: soal di level ini = total soal tampil - soal di level berikutnya
|
|
if ($soalDiLevelIni === $totalSoalTampil - $soalDiLevelBerikutnya && $soalDiLevelIni > 0 && $batasNaik > 0) {
|
|
return redirect()->back()->with('validation_error', [
|
|
'title' => 'Konfigurasi Quiz Tidak Valid',
|
|
'message' => "Quiz tidak bisa disimpan karena pada fase " . ($i + 1) . ", siswa yang gagal naik ke level berikutnya akan kehabisan soal. Mohon ulangi konfigurasi quiz agar lebih seimbang dan baik."
|
|
]);
|
|
}
|
|
}
|
|
*/
|
|
|
|
try {
|
|
// Simpan quiz dan soalnya ke DB
|
|
$quiz = Quizzes::create([
|
|
'judul' => $request->judul,
|
|
'deskripsi' => $request->deskripsi,
|
|
'matapelajaran_id' => $request->matapelajaran_id,
|
|
'total_soal' => $request->total_soal,
|
|
'total_soal_tampil' => $request->total_soal_tampil,
|
|
'waktu' => $request->waktu,
|
|
]);
|
|
|
|
QuizLevelSetting::create([
|
|
'quiz_id' => $quiz->id,
|
|
'jumlah_soal_per_level' => json_encode($request->jumlah_soal_per_level),
|
|
'level_awal' => session('level_awal') ?? 1,
|
|
'batas_naik_level' => json_encode($request->batas_naik_level),
|
|
'skor_level' => json_encode(session('skor_level')),
|
|
'kkm' => session('kkm') ?? 75,
|
|
]);
|
|
|
|
// Menampung data dalam array sebelum disimpan
|
|
$quizQuestionsData = [];
|
|
|
|
foreach ($preview as $row) {
|
|
$jawabanBenar = strtolower(trim($row[2]));
|
|
// Menyiapkan data untuk disimpan
|
|
$quizQuestionsData[] = [
|
|
'quiz_id' => $quiz->id,
|
|
'pertanyaan' => $row[1],
|
|
'opsi_a' => $row[4],
|
|
'opsi_b' => $row[5],
|
|
'opsi_c' => $row[6],
|
|
'opsi_d' => $row[7],
|
|
'jawaban_benar' => $jawabanBenar,
|
|
'level' => $row[3],
|
|
'skor' => $row[8],
|
|
'created_at' => now(),
|
|
'updated_at' => now(),
|
|
];
|
|
}
|
|
|
|
// Simpan semua data sekali gus menggunakan insert
|
|
if (!empty($quizQuestionsData)) {
|
|
QuizQuestions::insert($quizQuestionsData);
|
|
}
|
|
|
|
// Hapus session
|
|
$this->removeSession();
|
|
|
|
$matpel = MataPelajaran::findOrFail($request->matapelajaran_id);
|
|
// Cari siswa berdasarkan kelas dan tahun ajaran
|
|
$siswas = Siswa::where('kelas', $matpel['kelas'])
|
|
->where('tahun_ajaran', $matpel['tahun_ajaran'])
|
|
->get();
|
|
|
|
// Kirim notifikasi ke setiap siswa
|
|
foreach ($siswas as $siswa) {
|
|
$siswa->notify(new QuizBaruNotification($quiz));
|
|
}
|
|
|
|
\Log::info('Quiz berhasil diimport: ' . $request->judul);
|
|
return redirect()->route('quiz')->with('success_message', [
|
|
'title' => 'Berhasil',
|
|
'message' => 'Data quiz berhasil disimpan dan notifikasi telah dikirim ke siswa.'
|
|
]);
|
|
} catch (\Exception $e) {
|
|
\Log::error('Quiz Import Error: ' . $e->getMessage());
|
|
return redirect()->back()->with('validation_error', [
|
|
'title' => 'Terjadi Kesalahan',
|
|
'message' => 'Gagal menyimpan data quiz: ' . $e->getMessage()
|
|
]);
|
|
} catch (\Illuminate\Database\QueryException $e) {
|
|
\Log::error('Quiz Import Error: Database error - ' . $e->getMessage());
|
|
return redirect()->back()->with('validation_error', [
|
|
'title' => 'Terjadi Kesalahan',
|
|
'message' => 'Gagal menyimpan data quiz. Silakan coba lagi.'
|
|
]);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Display the specified resource.
|
|
*/
|
|
public function show(string $id)
|
|
{
|
|
//
|
|
}
|
|
|
|
/**
|
|
* Show the form for editing the specified resource.
|
|
*/
|
|
public function edit(string $id)
|
|
{
|
|
//
|
|
}
|
|
|
|
/**
|
|
* Update the specified resource in storage.
|
|
*/
|
|
public function update(Request $request, string $id)
|
|
{
|
|
//
|
|
}
|
|
|
|
/**
|
|
* Remove the specified resource from storage.
|
|
*/
|
|
public function destroy(Request $request)
|
|
{
|
|
try {
|
|
$quiz = Quizzes::findOrFail($request->formid);
|
|
$quiz->delete();
|
|
return redirect()->back()->with('success_message', [
|
|
'title' => 'Berhasil',
|
|
'message' => 'Data quiz berhasil dihapus.'
|
|
]);
|
|
} catch (\Throwable $th) {
|
|
return redirect()->back()->with('validation_error', [
|
|
'title' => 'Terjadi Kesalahan',
|
|
'message' => 'Gagal menghapus data quiz: ' . $th->getMessage()
|
|
]);
|
|
}
|
|
}
|
|
}
|