filled('search')) { $query->where('title', 'LIKE', '%' . $request->search . '%'); } $exams = $query->latest()->paginate(20); return view('pages.admin.exams.index', compact('exams')); } public function create() { // Tarik data materi untuk Select2 Modul $subjects = Subject::orderBy('name', 'asc')->get(); // Tarik data karyawan untuk Select2 Peserta (Bisa pilih banyak) $employees = User::orderBy('first_name', 'asc')->get(); return view('pages.admin.exams.create', compact('subjects', 'employees')); } public function store(Request $request) { $request->validate([ 'training_type' => 'required|in:matrix_plant,general,external', 'is_random_order' => 'nullable|boolean', ]); if ($request->training_type === 'external') { return back()->with('info', 'Fitur External Training dengan database HR sedang dalam tahap pengembangan.'); } // Validasi Tambahan: Peserta, Deadline, dan Array Ujian $request->validate([ 'employee_ids' => 'required|array|min:1', // Wajib pilih minimal 1 peserta 'employee_ids.*' => 'exists:users,id', 'deadline_date' => 'required|date|after_or_equal:today', // Deadline tidak boleh masa lalu 'exams' => 'required|array|min:1', 'exams.*.subject_id' => 'required|exists:subjects,id', 'exams.*.title' => 'nullable|string|max:255', 'exams.*.duration_minutes' => 'required|integer|min:1', 'exams.*.passing_grade' => 'required|integer|min:1|max:100', 'exams.*.max_retakes' => 'required|integer|min:0', ]); $isRandom = $request->has('is_random_order'); $trainingType = $request->training_type === 'general' ? 'product' : 'matrix_plant'; // 1. Simpan setiap modul sebagai sesi ujian tersendiri foreach ($request->exams as $examData) { $exam = Exam::create([ 'title' => $examData['title'] ?: Subject::find($examData['subject_id'])->name, 'subject_id' => $examData['subject_id'], 'training_type' => $trainingType, 'duration_minutes' => $examData['duration_minutes'], 'passing_grade' => $examData['passing_grade'], 'max_retakes' => $examData['max_retakes'], 'is_random_order' => $isRandom, 'is_active' => true, ]); // 2. Tugaskan (Assign) ujian yang baru dibuat ini ke SEMUA karyawan yang dipilih $assignments = []; foreach ($request->employee_ids as $userId) { $assignments[] = [ 'user_id' => $userId, 'exam_id' => $exam->id, 'deadline_date' => $request->deadline_date, 'status' => 'pending', 'created_at' => now(), 'updated_at' => now(), ]; } // Insert massal ke database agar prosesnya secepat kilat \Illuminate\Support\Facades\DB::table('exam_assignments')->insert($assignments); } return redirect()->route('admin.exams.index') ->with('success', count($request->exams) . ' Modul Ujian berhasil dibuat dan ditugaskan ke ' . count($request->employee_ids) . ' Karyawan!'); } // ============================================================================== // 2. AREA MANAJEMEN BANK SOAL // ============================================================================== public function questionBank(Request $request) { $creatorIds = QuestionBank::whereNotNull('created_by')->distinct()->pluck('created_by'); $creators = User::whereIn('id', $creatorIds)->orderBy('first_name', 'asc')->get(); $subjects = Subject::orderBy('name', 'asc')->get(); $departments = Department::orderBy('name', 'asc')->get(); $positions = Position::orderBy('name', 'asc')->get(); $totalQuestions = QuestionBank::count(); // Query utama dengan relasi $query = QuestionBank::with(['subject', 'department', 'creator', 'position']); // 1. FILTER GLOBAL SEARCH (Mencari di semua kolom & relasi) if ($request->filled('search')) { $search = $request->search; $query->where(function($q) use ($search) { // Cari di ID atau Teks Soal $q->where('id', 'LIKE', "%{$search}%") ->orWhere('question_text', 'LIKE', "%{$search}%") ->orWhere('question_type', 'LIKE', "%{$search}%") ->orWhere('question_level', 'LIKE', "%{$search}%") // Cari di Nama Materi ->orWhereHas('subject', function($sub) use ($search) { $sub->where('name', 'LIKE', "%{$search}%"); }) // Cari di Nama Departemen ->orWhereHas('department', function($dep) use ($search) { $dep->where('name', 'LIKE', "%{$search}%"); }) // Cari di Nama Jabatan ->orWhereHas('position', function($pos) use ($search) { $pos->where('name', 'LIKE', "%{$search}%"); }) // Cari di Nama Pembuat Soal ->orWhereHas('creator', function($usr) use ($search) { $usr->where('first_name', 'LIKE', "%{$search}%") ->orWhere('last_name', 'LIKE', "%{$search}%"); }); }); } // 2. FILTER DROPDOWN SPESIFIK if ($request->filled('creator_id')) { $query->where('created_by', $request->creator_id); } if ($request->filled('subject_id')) { $query->where('subject_id', $request->subject_id); } if ($request->filled('department_id')) { $query->where('department_id', $request->department_id); } if ($request->filled('position_id')) { $query->where('position_id', $request->position_id); } if ($request->filled('question_type')) { $query->where('question_type', $request->question_type); } if ($request->filled('question_level')) { $query->where('question_level', $request->question_level); } // ============================================================================== // 3. INTERCEPTOR UNTUK EXPORT (Excel & PDF) // ============================================================================== if ($request->action === 'export_excel' || $request->action === 'export_pdf') { $exportData = $query->latest()->get(); $exporterName = auth()->user()->first_name . ' ' . auth()->user()->last_name; $exportTime = now()->format('d F Y, H:i:s'); // LOGIKA EXPORT EXCEL (Download Paksa) if ($request->action === 'export_excel') { $fileName = 'Data_Bank_Soal_' . now()->format('Ymd_His') . '.xls'; return response()->streamDownload(function () use ($exportData, $exporterName, $exportTime, $request) { echo view('pages.admin.question-banks.export-report', compact('exportData', 'exporterName', 'exportTime', 'request'))->render(); }, $fileName, [ 'Content-Type' => 'application/vnd.ms-excel', 'Content-Disposition' => 'attachment; filename="'.$fileName.'"', ]); } // LOGIKA EXPORT PDF (Menggunakan Facade DomPDF) if ($request->action === 'export_pdf') { $fileName = 'Data_Bank_Soal_' . now()->format('Ymd_His') . '.pdf'; // Gunakan library PDF yang sudah ada di project Anda $pdf = \Barryvdh\DomPDF\Facade\Pdf::loadView('pages.admin.question-banks.export-report', compact('exportData', 'exporterName', 'exportTime', 'request')) ->setPaper('a4', 'landscape'); // Format landscape agar tabel muat return $pdf->download($fileName); } } // 4. JIKA BUKAN EXPORT, LANJUTKAN KE TAMPILAN TABEL NORMAL DENGAN PAGINATION $questions = $query->latest()->paginate(20); return view('pages.admin.question-banks.question-bank', compact('questions', 'creators', 'subjects', 'departments', 'positions', 'totalQuestions')); } // Nama diubah agar tidak bentrok dengan create() ujian public function createQuestion() { $departments = Department::orderBy('name', 'asc')->get(); $positions = Position::orderBy('name', 'asc')->get(); $subjects = Subject::orderBy('name', 'asc')->get(); return view('pages.admin.question-banks.create', compact('departments', 'positions', 'subjects')); } // Nama diubah agar spesifik untuk soal public function editQuestion($id) { $question = QuestionBank::findOrFail($id); $departments = Department::orderBy('name', 'asc')->get(); $positions = Position::orderBy('name', 'asc')->get(); $subjects = Subject::orderBy('name', 'asc')->get(); return view('pages.admin.question-banks.edit', compact('question', 'departments', 'positions', 'subjects')); } // Nama diubah agar spesifik untuk soal public function showQuestion($id) { $question = QuestionBank::with(['subject', 'department', 'position', 'creator'])->findOrFail($id); return view('pages.admin.question-banks.show', compact('question')); } public function bulkDelete(Request $request) { $request->validate([ 'question_ids' => 'required|array', 'question_ids.*' => 'exists:question_banks,id' // Diperbaiki dari tabel questions ke question_banks ]); try { // Diperbaiki dari model Question ke QuestionBank QuestionBank::whereIn('id', $request->question_ids)->delete(); return back()->with('success', count($request->question_ids) . ' Soal berhasil dihapus secara massal.'); } catch (\Exception $e) { return back()->with('error', 'Gagal menghapus soal: ' . $e->getMessage()); } } public function importView() { return view('pages.admin.exams.questions.import'); } // ============================================================================== // FUNGSI PROSES SIMPAN, EDIT, DAN HAPUS BANK SOAL // ============================================================================== public function storeQuestion(Request $request) { $request->validate([ 'subject_id' => 'required|exists:subjects,id', 'department_id' => 'nullable|exists:departments,id', 'position_id' => 'nullable|exists:positions,id', // VALIDASI INI SUDAH DISAMAKAN DENGAN FORM UI 'question_type' => 'required|in:single,multiple,true_false,descriptive', 'question_text' => 'required|string', 'score_weight' => 'required|numeric|min:1', 'duration' => 'nullable|numeric', 'passing_grade' => 'nullable|numeric', ]); $data = $request->all(); $data['created_by'] = auth()->id(); // Rekam siapa yang membuat soal // Bersihkan data yang tidak relevan berdasarkan jenis soal if ($request->question_type === 'descriptive') { $data['option_a'] = null; $data['option_b'] = null; $data['option_c'] = null; $data['option_d'] = null; $data['option_e'] = null; $data['correct_answer'] = null; } else { $data['essay_keywords'] = null; } // Jika tipe soal adalah Multiple (bisa centang lebih dari 1), ubah array menjadi JSON // if (isset($request->correct_answer) && is_array($request->correct_answer)) { // $data['correct_answer'] = json_encode($request->correct_answer); // } QuestionBank::create($data); return redirect()->route('admin.question-bank.index')->with('success', 'Soal baru berhasil ditambahkan ke Bank Soal!'); } public function updateQuestion(Request $request, $id) { $request->validate([ 'subject_id' => 'required|exists:subjects,id', 'department_id' => 'nullable|exists:departments,id', 'position_id' => 'nullable|exists:positions,id', 'question_type' => 'required|in:single,multiple,true_false,descriptive', 'question_text' => 'required|string', 'score_weight' => 'required|numeric|min:1', 'duration' => 'nullable|numeric', 'passing_grade' => 'nullable|numeric', ]); $question = QuestionBank::findOrFail($id); $data = $request->all(); // 1. Bersihkan data yang tidak relevan berdasarkan jenis soal if ($request->question_type === 'descriptive') { $data['option_a'] = null; $data['option_b'] = null; $data['option_c'] = null; $data['option_d'] = null; $data['option_e'] = null; $data['correct_answer'] = null; } else { $data['essay_keywords'] = null; } // 2. Jika tipe soal adalah Multiple (bisa centang lebih dari 1), ubah array menjadi JSON // if (is_array($request->correct_answer)) { // $data['correct_answer'] = json_encode($request->correct_answer); // } // 3. Simpan pembaruan $question->update($data); return redirect()->route('admin.question-bank.index')->with('success', 'Perubahan soal berhasil disimpan!'); } public function destroyQuestion($id) { try { $question = QuestionBank::findOrFail($id); $question->delete(); return back()->with('success', 'Soal berhasil dihapus.'); } catch (\Exception $e) { return back()->with('error', 'Gagal menghapus soal: ' . $e->getMessage()); } } }