orderByRaw("CASE WHEN start IS NULL OR start = '' THEN 1 ELSE 0 END, start ASC") ->get(); return $jharini->toJson(); } public function index(){ $data = []; $peserta = []; $guru = Guru::where('status', 'Aktif')->select('id', 'nama')->get(); return view('admin.pages.jadwal', compact('data', 'peserta', 'guru')); } public function calendarData(){ $jharini = Logbooks::orderByRaw("CASE WHEN start IS NULL OR start = '' THEN 1 ELSE 0 END, start ASC") ->with('Kelas.Guru', 'FotoLogbook') ->get(); return json_encode($jharini); } public function calendarCountData($id){ if ($id != 0){ $jharini = Logbooks::whereHas('kelas', function($query) use ($id) { $query->where('guru_id', $id); }) ->orderByRaw("CASE WHEN start IS NULL OR start = '' THEN 1 ELSE 0 END, start ASC") ->with('Kelas', 'FotoLogbook') ->get(); }else{ $jharini = Logbooks::orderByRaw("CASE WHEN start IS NULL OR start = '' THEN 1 ELSE 0 END, start ASC") ->with('Kelas', 'FotoLogbook') ->get(); } $total = $jharini->count(); $completed = $jharini->where('status', 'completed')->count(); $kelas = $jharini->pluck('kelas_id')->unique()->count(); $response = [ 'kelas' => $kelas, 'total' => $total, 'completed' => $completed, // 'data' => $jharini ]; return response()->json($response); } public function calendar(){ $guru = Guru::where('status', 'Aktif')->get(); return view('admin.pages.jadwal-calendar', compact('guru')); } // AJAX Endpoints untuk menampilkan data tanpa timeout public function getJadwalHariIni(){ $jharini = Logbooks::whereDate('tanggal', Carbon::today()) ->orderByRaw("CASE WHEN start IS NULL OR start = '' THEN 1 ELSE 0 END, start ASC") ->with(['Kelas.guru', 'Guru', 'FotoLogbook']) ->get(); return response()->json([ 'success' => true, 'data' => $jharini, 'count' => count($jharini) ]); } public function getJadwalBesok(){ $jbesok = Logbooks::whereDate('tanggal', Carbon::tomorrow()) ->orderByRaw("CASE WHEN start IS NULL OR start = '' THEN 1 ELSE 0 END, start ASC") ->with(['Kelas.guru', 'Guru', 'FotoLogbook']) ->get(); return response()->json([ 'success' => true, 'data' => $jbesok, 'count' => count($jbesok) ]); } public function getLogbookData(){ Carbon::setLocale('id'); $groupedLogbooks = Logbooks::orderBy('tanggal', 'asc') ->with(['Kelas.guru', 'Guru', 'FotoLogbook']) ->get() ->groupBy(function($item) { return \Carbon\Carbon::parse($item->tanggal)->translatedFormat('F Y'); }); $now = \Carbon\Carbon::now(); $sortedLogbooks = collect([ 'upcoming' => collect(), 'past' => collect(), ]); $logbookSummary = collect(); foreach ($groupedLogbooks as $bulanTahun => $items) { $firstItemDate = \Carbon\Carbon::parse($items->first()->tanggal); $category = $firstItemDate->greaterThanOrEqualTo($now->startOfMonth()) ? 'upcoming' : 'past'; $sortedLogbooks[$category][$bulanTahun] = $items; // Menyimpan jumlah logbook tiap bulan $logbookSummary[$bulanTahun] = [ 'jumlah_sesi' => $items->count(), 'jumlah_sesi_completed' => $items->where('status', 'completed')->count(), 'jumlah_siswa' => $items->pluck('Kelas')->filter()->flatMap->siswa->unique('id')->count(), 'jumlah_kelas' => $items->pluck('kelas_id')->unique()->count(), ]; } $logbooksUpcoming = $sortedLogbooks['upcoming']; $logbooksPast = $sortedLogbooks['past']->sortByDesc(function($items, $bulanTahun) { return \Carbon\Carbon::parse($items->first()->tanggal); }); return response()->json([ 'success' => true, 'upcoming' => $logbooksUpcoming, 'past' => $logbooksPast, 'summary' => $logbookSummary ]); } public function generateGAIndex() { $semesters = \App\Models\Kelas::whereNotNull('semester')->distinct()->pluck('semester'); $years = \App\Models\Kelas::whereNotNull('tahun_ajaran')->distinct()->pluck('tahun_ajaran'); return view('admin.pages.generate-ga', compact('semesters', 'years')); } public function hasilGAIndex() { return view('admin.pages.hasil-ga'); } public function jadwalMingguan() { $sesiList = \App\Models\Sesi::orderBy('id', 'asc')->get(); return view('admin.pages.jadwal-mingguan', compact('sesiList')); } public function jadwalKromosom() { $jadwalMaster = \App\Models\JadwalKelas::with(['kelas', 'guru', 'ruangan', 'sesi']) ->orderBy('hari', 'asc') ->orderBy('sesi_id', 'asc') ->get(); return view('admin.pages.jadwal-kromosom', compact('jadwalMaster')); } public function getJadwalMingguanData(Request $request) { $masterJadwal = \App\Models\JadwalKelas::with(['kelas', 'guru', 'ruangan', 'sesi'])->get(); $data = $masterJadwal->map(function ($j) { return [ 'id' => $j->id, 'hari' => $j->hari, 'sesi_id' => $j->sesi_id, 'kelas' => $j->kelas, 'guru' => $j->guru, 'ruangan' => $j->ruangan, 'sesi' => $j->sesi, 'is_master' => true ]; }); return response()->json([ 'success' => true, 'is_master' => true, 'data' => $data ]); } public function generateGA(Request $request) { $request->validate([ 'semester' => 'nullable|string', 'tahun_ajaran' => 'nullable|string', 'population_size' => 'nullable|numeric|min:10|max:200', 'crossover_rate' => 'nullable|numeric|min:0.1|max:1.0', 'mutation_rate' => 'nullable|numeric|min:0.01|max:1.0', 'max_generations' => 'nullable|numeric|min:10|max:2000', 'random_seed' => 'nullable|numeric', ]); try { $semester = $request->input('semester'); $tahunAjaran = $request->input('tahun_ajaran'); $popSize = (int) $request->input('population_size', 50); $crossRate = (float) $request->input('crossover_rate', 0.8); $mutRate = (float) $request->input('mutation_rate', 0.1); $maxGen = (int) $request->input('max_generations', 500); // Solusi A: Pool 10 Controlled Seeds Teruji (Bervariasi Terkontrol & Reproducible) $presetSeeds = [202, 101, 303, 404, 505, 606, 707, 808, 909, 1001]; $paramSignature = "ga_{$semester}_{$tahunAjaran}_{$popSize}_{$crossRate}_{$mutRate}"; $lastSignature = session('ga_last_param_signature'); $attemptIndex = session()->has('ga_attempt_index') ? (int) session('ga_attempt_index') : -1; if ($lastSignature !== $paramSignature || $attemptIndex < 0) { // Reset ke percobaan ke-1 (index 0) jika parameter diubah atau session baru $attemptIndex = 0; } else { // Lanjutkan ke percobaan berikutnya (berputar di 0..9) $attemptIndex = ($attemptIndex + 1) % count($presetSeeds); } session()->put('ga_last_param_signature', $paramSignature); session()->put('ga_attempt_index', $attemptIndex); $seed = $request->filled('random_seed') ? (int) $request->input('random_seed') : $presetSeeds[$attemptIndex]; $service = new \App\Services\Scheduling\ScheduleGeneratorService(); $result = $service->generate( semester: $semester, tahunAjaran: $tahunAjaran, populationSize: $popSize, crossoverRate: $crossRate, mutationRate: $mutRate, maxGenerations: $maxGen, seed: $seed ); $result['attemptNumber'] = $attemptIndex + 1; $result['seedUsed'] = $seed; $result['semester'] = $semester ?? 'Genap'; $result['tahunAjaran'] = $tahunAjaran ?? '2025/2026'; $result['populationSize'] = $popSize; $result['crossoverRate'] = $crossRate; $result['mutationRate'] = $mutRate; $result['maxGenerations'] = $maxGen; $statusMsg = $result['isOptimal'] ? "Penjadwalan GA Berhasil Optimal (Fitness: 1.00 pada Generasi ke-{$result['generations']}, Waktu: {$result['executionTime']}s)" : "Penjadwalan GA Selesai pada Generasi ke-{$result['generations']} (Fitness: " . round($result['bestChromosome']->fitness, 4) . ", Waktu: {$result['executionTime']}s)"; session()->put('ga_log_result', $result); return redirect('/admin/hasil-ga')->with('success', $statusMsg); } catch (\Exception $e) { return redirect('/admin/jadwal-mingguan')->with('error', 'Gagal memproses GA: ' . $e->getMessage()); } } public function exportExcelGA() { $result = session('ga_log_result'); if (!$result) { return redirect('/admin/generate-ga')->with('error', 'Belum ada hasil eksekusi GA. Silakan jalankan eksekusi terlebih dahulu.'); } $fileName = 'Simulasi_Algoritma_Genetika_Robotku_' . date('Ymd_His') . '.xlsx'; return \Maatwebsite\Excel\Facades\Excel::download(new \App\Exports\GaSimulationExport($result), $fileName); } public function exportExcelMingguan() { $jadwalMaster = \App\Models\JadwalKelas::with(['kelas', 'guru', 'ruangan', 'sesi']) ->orderBy('hari', 'asc') ->orderBy('sesi_id', 'asc') ->get(); if ($jadwalMaster->isEmpty()) { return redirect()->back()->with('error', 'Belum ada data jadwal kelas untuk di-export.'); } $fileName = 'Master_Jadwal_Kelas_Mingguan_' . date('Ymd_His') . '.xlsx'; return \Maatwebsite\Excel\Facades\Excel::download(new \App\Exports\JadwalMingguanExport($jadwalMaster), $fileName); } public function exportExcelGaPresentation() { $service = new \App\Services\Scheduling\GaPresentationSimulationService(); $simData = $service->generateSimulationData(); $fileName = 'Simulasi_GA_Presentasi_Robotku_' . date('Ymd_His') . '.xlsx'; return \Maatwebsite\Excel\Facades\Excel::download(new \App\Exports\GaPresentationExport($simData), $fileName); } public function generateLogbookFromJadwal(Request $request) { $request->validate([ 'tanggal_mulai' => 'required|date', 'tanggal_selesai' => 'required|date|after_or_equal:tanggal_mulai', ], [ 'tanggal_mulai.required' => 'Tanggal mulai harus diisi.', 'tanggal_selesai.required' => 'Tanggal selesai harus diisi.', 'tanggal_selesai.after_or_equal' => 'Tanggal selesai harus sama atau setelah tanggal mulai.', ]); try { $service = new \App\Services\LogbookGeneratorService(); $result = $service->generateFromJadwal( startDate: $request->input('tanggal_mulai'), endDate: $request->input('tanggal_selesai') ); $msg = "Berhasil memproses logbook dari Master Jadwal: {$result['created']} entri baru dibuat"; if ($result['updated'] > 0) { $msg .= ", {$result['updated']} logbook pending diperbarui"; } if ($result['skipped'] > 0) { $msg .= ", {$result['skipped']} logbook completed dilewati"; } $msg .= "."; return redirect()->back()->with('success', $msg); } catch (\Exception $e) { return redirect()->back()->with('error', 'Gagal generate logbook: ' . $e->getMessage()); } } }