route('login'); }); /* |-------------------------------------------------------------------------- | 2. AREA PROTEKSI AKSES UTAMA (AUTH & AUTENTIKASI) |-------------------------------------------------------------------------- */ Route::middleware(['auth'])->group(function () { // GLOBAL LOGOUT HANDLER Route::post('/logout', function (Request $request) { Auth::guard('web')->logout(); $request->session()->invalidate(); $request->session()->regenerateToken(); return redirect()->route('login'); })->name('logout'); /* * ==================================================================== * RUTE PAKSA GANTI PASSWORD (Diakses saat first login) * ==================================================================== */ Route::get('/force-change-password', function () { return view('livewire.pages.auth.force-change-password'); })->name('password.force-change'); Route::post('/force-change-password', function (Request $request) { $request->validate([ 'password' => 'required|string|min:8|confirmed', ]); $user = $request->user(); $user->password = Hash::make($request->password); $user->must_change_password = false; // Cabut status wajib ganti $user->save(); // Redirect cerdas berdasarkan role setelah ganti password if ($user->hasRole(['admin', 'trainer'])) { return redirect()->route('admin.dashboard')->with('success', 'Password default berhasil diganti!'); } return redirect()->route('cbt.dashboard')->with('success', 'Password berhasil diganti. Selamat datang!'); })->name('password.force-update'); /* * ==================================================================== * AREA APLIKASI UTAMA * (Hanya bisa diakses jika user SUDAH mengganti password default) * ==================================================================== */ Route::middleware(['force.password'])->group(function () { /* * A. AREA PANEL ADMIN & TRAINER (PREFIX: /admin) * Dilindungi oleh Spatie: Hanya admin atau trainer */ Route::prefix('admin')->name('admin.')->middleware(['role:admin|trainer'])->group(function () { // Dashboard Utama Admin Route::get('/dashboard', [DashboardController::class, 'index'])->name('dashboard'); Route::get('/api/live-exams', [DashboardController::class, 'getLiveExams'])->name('api.live-exams'); // DETAIL GRAFIK DEPARTEMEN Route::get('/dashboard/department/{id}/fulfillment', [DashboardController::class, 'departmentFulfillment'])->name('dashboard.dept-detail'); // ================================================================ // MODUL 1: BANK SOAL (QUESTIONS) // ================================================================ Route::get('/question-bank', [ExamManagementController::class, 'questionBank'])->name('question-bank.index'); Route::delete('exams/questions/bulk-delete', [ExamManagementController::class, 'bulkDelete'])->name('exams.questions.bulkDelete'); Route::get('exams/questions/import', [ExamManagementController::class, 'importView'])->name('exams.questions.import'); Route::post('exams/questions/import', [ExamManagementController::class, 'importProcess']); // PERBAIKAN: Rute 'create' WAJIB ditaruh di ATAS rute '{id}' Route::get('exams/questions/create', [ExamManagementController::class, 'createQuestion'])->name('exams.questions.create'); Route::post('exams/questions', [ExamManagementController::class, 'storeQuestion'])->name('exams.questions.store'); // Rute yang mengandung variabel dinamis {id} ditaruh paling bawah Route::get('exams/questions/{id}/edit', [ExamManagementController::class, 'editQuestion'])->name('exams.questions.edit'); Route::put('exams/questions/{id}', [ExamManagementController::class, 'updateQuestion'])->name('exams.questions.update'); Route::delete('exams/questions/{id}', [ExamManagementController::class, 'destroyQuestion'])->name('exams.questions.destroy'); Route::get('exams/questions/{id}', [ExamManagementController::class, 'showQuestion'])->name('exams.questions.show'); // ================================================================ // MODUL 2: MANAJEMEN SESI UJIAN (CBT EXAMS) // ================================================================ Route::get('/exams', [ExamManagementController::class, 'index'])->name('exams.index'); Route::get('/exams/create', [ExamManagementController::class, 'create'])->name('exams.create'); Route::post('/exams', [ExamManagementController::class, 'store'])->name('exams.store'); // ================================================================ // MODUL 3: EXPORT & IMPORT DATA (KARYAWAN & MASTER DATA) // Resource diletakkan di bawah custom route agar tidak ditimpa // ================================================================ Route::get('employees/export/excel', [EmployeeController::class, 'exportExcel'])->name('employees.export.excel'); Route::get('employees/export/pdf', [EmployeeController::class, 'exportPdf'])->name('employees.export.pdf'); Route::get('employees/import', [EmployeeController::class, 'importView'])->name('employees.import'); Route::get('employees/import/template', [EmployeeController::class, 'downloadTemplate'])->name('employees.import.template'); Route::post('employees/import/preview', [EmployeeController::class, 'importPreview'])->name('employees.import.preview'); Route::post('employees/import/process', [EmployeeController::class, 'importProcess'])->name('employees.import.process'); Route::get('departments/export/excel', [DepartmentController::class, 'exportExcel'])->name('departments.export.excel'); Route::get('departments/export/pdf', [DepartmentController::class, 'exportPdf'])->name('departments.export.pdf'); Route::get('positions/export/excel', [PositionController::class, 'exportExcel'])->name('positions.export.excel'); Route::get('positions/export/pdf', [PositionController::class, 'exportPdf'])->name('positions.export.pdf'); Route::resource('departments', DepartmentController::class); Route::resource('positions', PositionController::class); Route::resource('employees', EmployeeController::class); Route::resource('subjects', SubjectController::class); // ================================================================ // MODUL 4: MANAJEMEN DOKUMEN SOP // ================================================================ Route::get('/sops/open/{id}', [DashboardController::class, 'openSopFile'])->name('sops.open'); Route::get('/sops', [DashboardController::class, 'sopIndex'])->name('sops.index'); }); /* * B. AREA PANEL REPORT TRAINING * Dilindungi oleh Spatie: Hanya admin atau trainer */ Route::prefix('reports')->name('reports.')->middleware(['role:admin|trainer'])->group(function () { Route::get('/training', [ReportController::class, 'training'])->name('training'); Route::get('/training/{user}', [ReportController::class, 'showTraining'])->name('training.show'); }); /* * C. AREA PORTAL KARYAWAN / TRAINEE (PREFIX: /portal) * Dilindungi oleh Spatie: Hanya karyawan */ Route::prefix('portal')->name('cbt.')->middleware(['role:karyawan'])->group(function () { Route::get('/dashboard', [UserDashboardController::class, 'index'])->name('dashboard'); }); }); // End of force.password middleware }); // End of auth middleware /* |-------------------------------------------------------------------------- | 3. FILE AUTENTIKASI BAWAAN (LARAVEL BREEZE / VOLT) |-------------------------------------------------------------------------- */ require __DIR__.'/auth.php';