diff --git a/back/app/Http/Controllers/TalkController.php b/back/app/Http/Controllers/TalkController.php index cd6bad1..b6a023e 100644 --- a/back/app/Http/Controllers/TalkController.php +++ b/back/app/Http/Controllers/TalkController.php @@ -3,8 +3,10 @@ namespace App\Http\Controllers; use App\Models\Talk; +use App\Services\SampleTalkGenerator; use Carbon\Carbon; use Illuminate\Http\Request; +use Illuminate\Support\Facades\DB; use Illuminate\Support\Facades\Validator; class TalkController extends Controller @@ -34,7 +36,7 @@ public function index(Request $request) $user = $request->user(); // Seuls les speakers peuvent voir leurs talks - if (! $user->isSpeaker() && ! $user->isOrganizer() && ! $user->isSuperadmin()) { + if (! $user->isSpeaker() && ! $user->isOrganizer() && ! $user->isSuperAdmin()) { /** * Accès non autorisé - L'utilisateur n'a pas les droits requis * @@ -196,7 +198,7 @@ public function show(Request $request, string $id) $user = $request->user(); // Seul le speaker propriétaire ou un admin peut voir le talk - if ($talk->speaker_id !== $user->id && ! $user->isOrganizer() && ! $user->isSuperadmin()) { + if ($talk->speaker_id !== $user->id && ! $user->isOrganizer() && ! $user->isSuperAdmin()) { /** * Accès non autorisé - L'utilisateur n'est pas le présentateur ou un administrateur * @@ -256,7 +258,7 @@ public function update(Request $request, string $id) // CAS 1: SCHEDULING (par organisateur ou superadmin) if ($isScheduling) { // Vérification des autorisations pour le scheduling - if (! $user->isOrganizer() && ! $user->isSuperadmin()) { + if (! $user->isOrganizer() && ! $user->isSuperAdmin()) { /** * Accès non autorisé - Seul un organisateur peut programmer une conférence * @@ -547,7 +549,7 @@ public function updateStatus(Request $request, string $id) $user = $request->user(); // Seul un organizer ou superadmin peut changer le statut - if (! $user->isOrganizer() && ! $user->isSuperadmin()) { + if (! $user->isOrganizer() && ! $user->isSuperAdmin()) { /** * Accès non autorisé - Seuls les organisateurs peuvent modifier le statut * @@ -599,6 +601,28 @@ public function updateStatus(Request $request, string $id) ]); } + /** + * Générer des conférences de démonstration autour de la date actuelle. + * + * Réservé aux superadmins afin de rapidement remplir l'application avec des + * talks passés, présents et futurs utiles pour la démonstration. + */ + public function generateSample(Request $request, SampleTalkGenerator $generator) + { + $user = $request->user(); + + if (! $user || ! $user->isSuperAdmin()) { + return response()->json(['message' => 'Unauthorized'], 403); + } + + $stats = DB::transaction(fn () => $generator->generate()); + + return response()->json([ + 'message' => 'Sample talks generated successfully', + 'stats' => $stats, + ]); + } + /** * Lister les conférences publiques * diff --git a/back/app/Http/Controllers/UserController.php b/back/app/Http/Controllers/UserController.php index a24f921..c99140e 100644 --- a/back/app/Http/Controllers/UserController.php +++ b/back/app/Http/Controllers/UserController.php @@ -340,7 +340,7 @@ public function update(Request $request, User $user) $authUser = $request->user(); // Vérifier si l'utilisateur peut mettre à jour ce profil - if (! ($authUser->id === $user->id || $authUser->isSuperadmin() || $authUser->isOrganizer())) { + if (! ($authUser->id === $user->id || $authUser->isSuperAdmin() || $authUser->isOrganizer())) { /** * Accès non autorisé - L'utilisateur n'a pas les droits requis * @@ -400,7 +400,7 @@ public function update(Request $request, User $user) ]; // Seuls les superadmin et organizer peuvent modifier les rôles - if ($authUser->isSuperadmin() || $authUser->isOrganizer()) { + if ($authUser->isSuperAdmin() || $authUser->isOrganizer()) { /** * Rôle attribué à l'utilisateur * @@ -439,7 +439,7 @@ public function update(Request $request, User $user) } // Mettre à jour le rôle si autorisé - if (($authUser->isSuperadmin() || $authUser->isOrganizer()) && $request->filled('role')) { + if (($authUser->isSuperAdmin() || $authUser->isOrganizer()) && $request->filled('role')) { $userData['role'] = $request->input('role'); } @@ -482,7 +482,7 @@ public function destroy(Request $request, User $user) $authUser = $request->user(); // Seuls les superadmin et organizer peuvent supprimer des utilisateurs - if (! ($authUser->isSuperadmin() || $authUser->isOrganizer())) { + if (! ($authUser->isSuperAdmin() || $authUser->isOrganizer())) { /** * Accès non autorisé - L'utilisateur n'a pas les droits requis * @@ -496,7 +496,7 @@ public function destroy(Request $request, User $user) } // Un organizer ne peut pas supprimer un superadmin - if ($authUser->isOrganizer() && $user->isSuperadmin()) { + if ($authUser->isOrganizer() && $user->isSuperAdmin()) { /** * Accès non autorisé - Un organisateur ne peut pas supprimer un superadmin * @@ -553,7 +553,7 @@ public function promoteToSpeaker(Request $request, User $user) $authUser = $request->user(); // Vérifier les permissions - if (! ($authUser->isSuperadmin() || $authUser->isOrganizer())) { + if (! ($authUser->isSuperAdmin() || $authUser->isOrganizer())) { /** * Accès non autorisé - L'utilisateur n'a pas les droits requis * @@ -613,7 +613,7 @@ public function demoteToPublic(Request $request, User $user) $authUser = $request->user(); // Vérifier les permissions - if (! ($authUser->isSuperadmin() || $authUser->isOrganizer())) { + if (! ($authUser->isSuperAdmin() || $authUser->isOrganizer())) { /** * Accès non autorisé - L'utilisateur n'a pas les droits requis * diff --git a/back/app/Services/SampleTalkGenerator.php b/back/app/Services/SampleTalkGenerator.php new file mode 100644 index 0000000..72882d1 --- /dev/null +++ b/back/app/Services/SampleTalkGenerator.php @@ -0,0 +1,123 @@ +count(3)->create(); + } + + $rooms = Room::all(); + if ($rooms->isEmpty()) { + return [ + 'created' => 0, + 'scheduled' => 0, + 'pending' => 0, + 'accepted' => 0, + 'rejected' => 0, + ]; + } + + $createdTalks = collect(); + + $createdTalks = $createdTalks->merge( + $this->generatePendingTalks() + ); + + $createdTalks = $createdTalks->merge( + $this->generateAcceptedTalks() + ); + + $createdTalks = $createdTalks->merge( + $this->generateRejectedTalks() + ); + + $createdTalks = $createdTalks->merge( + $this->generateScheduledTalks($reference, $rooms) + ); + + return [ + 'created' => $createdTalks->count(), + 'scheduled' => $createdTalks->where('status', 'scheduled')->count(), + 'pending' => $createdTalks->where('status', 'pending')->count(), + 'accepted' => $createdTalks->where('status', 'accepted')->count(), + 'rejected' => $createdTalks->where('status', 'rejected')->count(), + ]; + } + + private function generatePendingTalks(): Collection + { + return Talk::factory()->count(10)->pending()->create(); + } + + private function generateAcceptedTalks(): Collection + { + return Talk::factory()->count(5)->accepted()->create(); + } + + private function generateRejectedTalks(): Collection + { + return Talk::factory()->count(3)->state(['status' => 'rejected'])->create(); + } + + private function generateScheduledTalks(Carbon $reference, Collection $rooms): Collection + { + $created = collect(); + + for ($i = 7; $i >= 1; $i--) { + $date = $reference->copy()->subDays($i)->format('Y-m-d'); + $created->push($this->createScheduledTalk($date, '10:00', '11:30', $rooms->random()->id)); + $created->push($this->createScheduledTalk($date, '14:30', '15:30', $rooms->random()->id)); + $created->push($this->createScheduledTalk($date, '16:45', '18:00', $rooms->random()->id)); + } + + $today = $reference->format('Y-m-d'); + $created->push($this->createScheduledTalk($today, '09:30', '10:30', $rooms->random()->id)); + $created->push($this->createScheduledTalk($today, '11:00', '12:00', $rooms->random()->id)); + $created->push($this->createScheduledTalk($today, '14:00', '15:00', $rooms->random()->id)); + $created->push($this->createScheduledTalk($today, '16:30', '17:30', $rooms->random()->id)); + $created->push($this->createScheduledTalk($today, '18:00', '19:00', $rooms->random()->id)); + + for ($i = 1; $i <= 14; $i++) { + if ($i % 2 === 0) { + continue; + } + + $date = $reference->copy()->addDays($i)->format('Y-m-d'); + $created->push($this->createScheduledTalk($date, '09:00', '10:00', $rooms->random()->id)); + $created->push($this->createScheduledTalk($date, '11:30', '12:30', $rooms->random()->id)); + $created->push($this->createScheduledTalk($date, '13:30', '14:30', $rooms->random()->id)); + $created->push($this->createScheduledTalk($date, '15:00', '16:30', $rooms->random()->id)); + $created->push($this->createScheduledTalk($date, '17:30', '18:45', $rooms->random()->id)); + } + + return $created; + } + + private function createScheduledTalk(string $date, string $start, string $end, int $roomId): Talk + { + /** @var Talk $talk */ + $talk = Talk::factory()->create([ + 'status' => 'scheduled', + 'scheduled_date' => $date, + 'start_time' => $start, + 'end_time' => $end, + 'room_id' => $roomId, + ]); + + return $talk; + } +} diff --git a/back/database/seeders/TalkSeeder.php b/back/database/seeders/TalkSeeder.php index 06271c2..594e275 100644 --- a/back/database/seeders/TalkSeeder.php +++ b/back/database/seeders/TalkSeeder.php @@ -2,9 +2,7 @@ namespace Database\Seeders; -use App\Models\Room; -use App\Models\Talk; -use Carbon\Carbon; +use App\Services\SampleTalkGenerator; use Illuminate\Database\Seeder; class TalkSeeder extends Seeder @@ -14,80 +12,6 @@ class TalkSeeder extends Seeder */ public function run(): void { - // Date actuelle - $now = Carbon::now(); - - // --- TALKS EN ATTENTE --- - Talk::factory()->count(10)->pending()->create(); - - // --- TALKS ACCEPTÉS --- - Talk::factory()->count(5)->accepted()->create(); - - // --- TALKS REJETÉS --- - Talk::factory()->count(3)->state(['status' => 'rejected'])->create(); - - // --- TALKS PROGRAMMÉS --- - // Vérifier que des salles existent - if (Room::count() === 0) { - Room::factory()->count(3)->create(); - } - $rooms = Room::all(); - - // 1. Talks passés (dernières semaines) - for ($i = 7; $i >= 1; $i--) { - $pastDate = $now->copy()->subDays($i)->format('Y-m-d'); - - // Matin - $this->createScheduledTalk($pastDate, '10:00', '11:30', $rooms->random()->id); - - // Après-midi - $this->createScheduledTalk($pastDate, '14:30', '15:30', $rooms->random()->id); - $this->createScheduledTalk($pastDate, '16:45', '18:00', $rooms->random()->id); - } - - // 2. Talks aujourd'hui - $today = $now->format('Y-m-d'); - - // Matin - $this->createScheduledTalk($today, '09:30', '10:30', $rooms->random()->id); - $this->createScheduledTalk($today, '11:00', '12:00', $rooms->random()->id); - - // Après-midi - $this->createScheduledTalk($today, '14:00', '15:00', $rooms->random()->id); - $this->createScheduledTalk($today, '16:30', '17:30', $rooms->random()->id); - $this->createScheduledTalk($today, '18:00', '19:00', $rooms->random()->id); - - // 3. Talks futurs (prochaines semaines) - for ($i = 1; $i <= 14; $i++) { - $futureDate = $now->copy()->addDays($i)->format('Y-m-d'); - - // Ne pas générer pour tous les jours, juste certains - if ($i % 2 == 0) { - continue; - } - - // Matin - $this->createScheduledTalk($futureDate, '09:00', '10:00', $rooms->random()->id); - $this->createScheduledTalk($futureDate, '11:30', '12:30', $rooms->random()->id); - - // Après-midi - $this->createScheduledTalk($futureDate, '13:30', '14:30', $rooms->random()->id); - $this->createScheduledTalk($futureDate, '15:00', '16:30', $rooms->random()->id); - $this->createScheduledTalk($futureDate, '17:30', '18:45', $rooms->random()->id); - } - } - - /** - * Crée un talk programmé avec la date et les heures spécifiées - */ - private function createScheduledTalk($date, $startTime, $endTime, $roomId) - { - Talk::factory()->create([ - 'status' => 'scheduled', - 'scheduled_date' => $date, - 'start_time' => $startTime, - 'end_time' => $endTime, - 'room_id' => $roomId, - ]); + app(SampleTalkGenerator::class)->generate(); } } diff --git a/back/routes/api.php b/back/routes/api.php index e515953..40ff8c1 100644 --- a/back/routes/api.php +++ b/back/routes/api.php @@ -34,6 +34,7 @@ Route::get('/talks/{id}', [TalkController::class, 'show']); Route::put('/talks/{id}', [TalkController::class, 'update']); Route::delete('/talks/{id}', [TalkController::class, 'destroy']); + Route::post('/talks/generate-sample', [TalkController::class, 'generateSample']); // Routes pour les favoris Route::get('/user/favorites', [FavoriteController::class, 'index']); diff --git a/front/src/pages/account/Index.tsx b/front/src/pages/account/Index.tsx index 709707b..a400ea9 100644 --- a/front/src/pages/account/Index.tsx +++ b/front/src/pages/account/Index.tsx @@ -1,5 +1,6 @@ import { useAuth } from "@/auth/useAuth"; import { Link, useLoaderData, useNavigate } from "@tanstack/react-router"; +import { toast } from "sonner"; import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; import { Button } from "@/components/ui/button"; @@ -72,6 +73,7 @@ function AccountPage() { const [isSaving, setIsSaving] = React.useState(false); const [showPasswords, setShowPasswords] = React.useState(false); + const [isGeneratingTalks, setIsGeneratingTalks] = React.useState(false); const [profilePicture, setProfilePicture] = React.useState(null); const [previewUrl, setPreviewUrl] = React.useState(""); @@ -317,6 +319,75 @@ function AccountPage() { [user, passwordForm, clearPasswordMessages, logout, navigate], ); + const handleGenerateSampleTalks = React.useCallback(async () => { + if (!user) { + toast.error( + "Vous devez être connecté avec un compte superadmin pour réaliser cette action.", + ); + return; + } + + setIsGeneratingTalks(true); + toast.info("Génération des conférences de démonstration en cours..."); + + try { + await fetch(`${API_BASE_URL}/api/sanctum/csrf-cookie`, { + credentials: "include", + headers: { + Accept: "application/json", + }, + }); + + const csrfToken = document.cookie + .split("; ") + .find((row) => row.startsWith("XSRF-TOKEN=")) + ?.split("=")[1]; + + if (!csrfToken) { + throw new Error("Impossible de récupérer le token CSRF"); + } + + const response = await fetch( + `${API_BASE_URL}/api/talks/generate-sample`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + Accept: "application/json", + "X-XSRF-TOKEN": decodeURIComponent(csrfToken), + }, + credentials: "include", + }, + ); + + if (!response.ok) { + const errorData = await response.json().catch(() => null); + const message = + errorData?.message || + `Erreur ${response.status}: ${response.statusText}`; + throw new Error(message); + } + + const payload = await response.json().catch(() => null); + + const scheduledCount = payload?.stats?.scheduled; + + toast.success( + scheduledCount + ? `${scheduledCount} conférences programmées ont été créées avec succès.` + : "Conférences générées avec succès.", + ); + } catch (error) { + const message = + error instanceof Error + ? error.message + : "Erreur lors de la génération des conférences"; + toast.error(message); + } finally { + setIsGeneratingTalks(false); + } + }, [user]); + // Si en chargement, afficher un indicateur if (loading) { return
Chargement...
; @@ -572,6 +643,30 @@ function AccountPage() { + {user.role === "superadmin" && ( + + + Outils superadmin + + Générer rapidement des conférences de démonstration autour de la + date actuelle. + + + + + + + )} + {user.role === "public" && (