where(function($q) { $q->whereNull('expires_at') ->orWhere('expires_at', '>=', now()); }) ->orderBy('created_at', 'desc') ->take(3) ->get() ->map(function($job) { $job->description = strip_tags(\Illuminate\Support\Str::markdown($job->description)); return $job; }); return Inertia::render('Welcome', [ 'canLogin' => Route::has('login'), 'latestJobs' => $latestJobs, ]); }); Route::get('/politique-de-confidentialite', function () { return Inertia::render('Public/PrivacyPolicy'); })->name('privacy-policy'); use App\Models\Candidate; use App\Models\Attempt; Route::get('/dashboard', function () { $stats = []; $quizzes = []; $topCandidates = []; if (auth()->user()->isAdmin()) { $user = auth()->user(); $isHR = $user->role === 'gestionnaire_rh'; $allCandidates = Candidate::with(['attempts'])->get(); $stats = [ 'total_candidates' => Candidate::count(), ]; if (!$isHR) { $stats['selected_candidates'] = Candidate::where('is_selected', true)->count(); $stats['finished_tests'] = Attempt::whereNotNull('finished_at')->count(); $stats['average_score'] = round($allCandidates->avg('weighted_score') ?? 0, 1); $stats['best_score'] = round($allCandidates->max('weighted_score') ?? 0, 1); } $query = Candidate::with(['user', 'attempts']); if ($isHR) { // For HR, just show latest candidates, no specific "Top" ranking $candidates = $query->latest()->take(10)->get(); } else { $candidates = $query->get()->sortByDesc('weighted_score')->take(10); } $topCandidates = $candidates->map(function($candidate) use ($isHR) { return [ 'id' => $candidate->id, 'name' => $candidate->user->name, 'email' => $candidate->user->email, 'status' => $candidate->status, 'weighted_score' => $isHR ? null : $candidate->weighted_score, 'ai_analysis' => $isHR ? null : $candidate->ai_analysis ]; }) ->values() ->all(); } else { $candidate = auth()->user()->candidate; if ($candidate) { $candidate->load('jobPosition.quizzes'); } $quizzes = ($candidate && $candidate->jobPosition) ? $candidate->jobPosition->quizzes : collect(); $quizzes = $quizzes->map(function($quiz) use ($candidate) { $quiz->has_finished_attempt = $candidate ? $candidate->attempts()->where('quiz_id', $quiz->id)->whereNotNull('finished_at')->exists() : false; return $quiz; }); } return Inertia::render('Dashboard', [ 'stats' => $stats, 'quizzes' => $quizzes, 'top_candidates' => $topCandidates ]); })->middleware(['auth', 'verified'])->name('dashboard'); // Public Job Routes Route::get('/jobs', [App\Http\Controllers\PublicJobApplicationController::class, 'index'])->name('jobs.index'); Route::get('/jobs/{jobPosition}', [App\Http\Controllers\PublicJobApplicationController::class, 'show'])->name('jobs.show'); Route::post('/jobs/{jobPosition}/apply', [App\Http\Controllers\PublicJobApplicationController::class, 'store'])->name('jobs.apply'); Route::middleware('auth')->group(function () { Route::get('/profile', [ProfileController::class, 'edit'])->name('profile.edit'); Route::patch('/profile', [ProfileController::class, 'update'])->name('profile.update'); Route::delete('/profile', [ProfileController::class, 'destroy'])->name('profile.destroy'); // Admin Routes Route::middleware('admin')->prefix('admin')->name('admin.')->group(function () { Route::get('/comparative', [\App\Http\Controllers\CandidateController::class, 'comparative'])->middleware('restrict_hr')->name('comparative'); Route::get('/candidates/selected', [\App\Http\Controllers\CandidateController::class, 'selectedCandidates'])->name('candidates.selected'); Route::get('/candidates/map', [\App\Http\Controllers\CandidateController::class, 'map'])->name('candidates.map'); Route::post('/candidates/update-order', [\App\Http\Controllers\CandidateController::class, 'updateOrder'])->name('candidates.update-order'); Route::resource('candidates', \App\Http\Controllers\CandidateController::class)->only(['index', 'store', 'show', 'destroy', 'update']); Route::patch('/candidates/{candidate}/notes', [\App\Http\Controllers\CandidateController::class, 'updateNotes'])->name('candidates.update-notes'); Route::patch('/candidates/{candidate}/scores', [\App\Http\Controllers\CandidateController::class, 'updateScores'])->name('candidates.update-scores'); Route::patch('/candidates/{candidate}/position', [\App\Http\Controllers\CandidateController::class, 'updatePosition'])->name('candidates.update-position'); Route::patch('/candidates/{candidate}/tenant', [\App\Http\Controllers\CandidateController::class, 'updateTenant'])->name('candidates.update-tenant'); Route::patch('/candidates/{candidate}/toggle-selection', [\App\Http\Controllers\CandidateController::class, 'toggleSelection'])->name('candidates.toggle-selection'); Route::post('/candidates/{candidate}/analyze', [\App\Http\Controllers\AIAnalysisController::class, 'analyze'])->middleware('restrict_hr')->name('candidates.analyze'); Route::post('/candidates/{candidate}/reset-password', [\App\Http\Controllers\CandidateController::class, 'resetPassword'])->name('candidates.reset-password'); Route::get('/documents/{document}', [\App\Http\Controllers\DocumentController::class, 'show'])->name('documents.show'); Route::get('/candidates/{candidate}/export-dossier', [\App\Http\Controllers\Admin\CandidateExportController::class, 'exportDossier'])->name('candidates.export-dossier'); Route::get('/candidates/{candidate}/export-zip', [\App\Http\Controllers\Admin\CandidateExportController::class, 'exportZip'])->name('candidates.export-zip'); Route::resource('quizzes', \App\Http\Controllers\QuizController::class)->middleware('restrict_hr')->only(['index', 'store', 'show', 'update', 'destroy']); Route::resource('job-positions', \App\Http\Controllers\JobPositionController::class)->only(['index', 'store', 'update', 'destroy']); Route::post('/job-positions/ai-fpt', [\App\Http\Controllers\Admin\JobPositionAiHelperController::class, 'generate'])->name('job-positions.ai-fpt'); Route::resource('quizzes.questions', \App\Http\Controllers\QuestionController::class)->middleware('restrict_hr')->only(['store', 'update', 'destroy']); Route::resource('tenants', \App\Http\Controllers\TenantController::class)->only(['index', 'store', 'update', 'destroy']); Route::resource('users', \App\Http\Controllers\UserController::class)->middleware('restrict_hr')->except(['show', 'create', 'edit']); Route::post('/users/{user}/reset-password', [\App\Http\Controllers\UserController::class, 'resetPassword'])->middleware('restrict_hr')->name('users.reset-password'); Route::get('/backup', [\App\Http\Controllers\BackupController::class, 'download'])->name('backup'); Route::delete('/attempts/{attempt}', [\App\Http\Controllers\AttemptController::class, 'destroy'])->name('attempts.destroy'); Route::patch('/answers/{answer}/score', [\App\Http\Controllers\AttemptController::class, 'updateAnswerScore'])->name('answers.update-score'); Route::get('/logs', [\App\Http\Controllers\Admin\LoginLogController::class, 'index'])->name('logs.index'); }); // Candidate Routes Route::get('/quizzes/{quiz}', [\App\Http\Controllers\AttemptController::class, 'show'])->name('quizzes.take'); Route::post('/attempts/{attempt}/save', [\App\Http\Controllers\AttemptController::class, 'saveAnswer'])->name('attempts.save'); Route::post('/attempts/{attempt}/finish', [\App\Http\Controllers\AttemptController::class, 'finish'])->name('attempts.finish'); // Security Honeypots Route::get('/documents/private', [\App\Http\Controllers\Api\CandidateHoneypotController::class, 'logDirectoryTraversal']); Route::get('/documents/private/{filename}', [\App\Http\Controllers\Api\CandidateHoneypotController::class, 'downloadFakeFile']); Route::patch('/api/candidate/me', [\App\Http\Controllers\Api\CandidateHoneypotController::class, 'logMassAssignment']); }); require __DIR__.'/auth.php';