diff --git a/app/api/admin/feedback/route.ts b/app/api/admin/feedback/route.ts new file mode 100644 index 0000000..938351d --- /dev/null +++ b/app/api/admin/feedback/route.ts @@ -0,0 +1,92 @@ +import { NextResponse } from "next/server"; +import { auth } from "@/lib/auth"; +import { prisma } from "@/lib/prisma"; +import { Role } from "@/prisma/generated/prisma/client"; + +export async function GET() { + try { + const session = await auth(); + if (!session?.user?.id) { + return NextResponse.json({ error: "Non authentifié" }, { status: 401 }); + } + + if (session.user.role !== Role.ADMIN) { + return NextResponse.json({ error: "Accès refusé" }, { status: 403 }); + } + + // Récupérer tous les feedbacks avec les détails de l'événement et de l'utilisateur + const feedbacks = await prisma.eventFeedback.findMany({ + include: { + event: { + select: { + id: true, + name: true, + date: true, + type: true, + }, + }, + user: { + select: { + id: true, + username: true, + email: true, + }, + }, + }, + orderBy: { + createdAt: "desc", + }, + }); + + // Calculer les statistiques par événement + const eventStats = await prisma.eventFeedback.groupBy({ + by: ["eventId"], + _avg: { + rating: true, + }, + _count: { + id: true, + }, + }); + + // Récupérer les détails des événements pour les stats + const eventIds = eventStats.map((stat) => stat.eventId); + const events = await prisma.event.findMany({ + where: { + id: { + in: eventIds, + }, + }, + select: { + id: true, + name: true, + date: true, + type: true, + }, + }); + + // Combiner les stats avec les détails des événements + const statsWithDetails = eventStats.map((stat) => { + const event = events.find((e) => e.id === stat.eventId); + return { + eventId: stat.eventId, + eventName: event?.name || "Événement supprimé", + eventDate: event?.date || null, + eventType: event?.type || null, + averageRating: stat._avg.rating || 0, + feedbackCount: stat._count.id, + }; + }); + + return NextResponse.json({ + feedbacks, + statistics: statsWithDetails, + }); + } catch (error) { + console.error("Error fetching feedbacks:", error); + return NextResponse.json( + { error: "Erreur lors de la récupération des feedbacks" }, + { status: 500 } + ); + } +} diff --git a/app/api/events/[id]/route.ts b/app/api/events/[id]/route.ts new file mode 100644 index 0000000..a407f73 --- /dev/null +++ b/app/api/events/[id]/route.ts @@ -0,0 +1,31 @@ +import { NextResponse } from "next/server"; +import { prisma } from "@/lib/prisma"; + +export async function GET( + request: Request, + { params }: { params: Promise<{ id: string }> } +) { + try { + const { id } = await params; + + const event = await prisma.event.findUnique({ + where: { id }, + }); + + if (!event) { + return NextResponse.json( + { error: "Événement introuvable" }, + { status: 404 } + ); + } + + return NextResponse.json(event); + } catch (error) { + console.error("Error fetching event:", error); + return NextResponse.json( + { error: "Erreur lors de la récupération de l'événement" }, + { status: 500 } + ); + } +} + diff --git a/app/api/feedback/[eventId]/route.ts b/app/api/feedback/[eventId]/route.ts new file mode 100644 index 0000000..150b622 --- /dev/null +++ b/app/api/feedback/[eventId]/route.ts @@ -0,0 +1,108 @@ +import { NextResponse } from "next/server"; +import { auth } from "@/lib/auth"; +import { prisma } from "@/lib/prisma"; + +export async function POST( + request: Request, + { params }: { params: Promise<{ eventId: string }> } +) { + try { + const session = await auth(); + if (!session?.user?.id) { + return NextResponse.json({ error: "Non authentifié" }, { status: 401 }); + } + + const { eventId } = await params; + const body = await request.json(); + const { rating, comment } = body; + + // Valider la note (1-5) + if (!rating || rating < 1 || rating > 5) { + return NextResponse.json( + { error: "La note doit être entre 1 et 5" }, + { status: 400 } + ); + } + + // Vérifier que l'événement existe + const event = await prisma.event.findUnique({ + where: { id: eventId }, + }); + + if (!event) { + return NextResponse.json( + { error: "Événement introuvable" }, + { status: 404 } + ); + } + + // Créer ou mettre à jour le feedback (unique par utilisateur/événement) + const feedback = await prisma.eventFeedback.upsert({ + where: { + userId_eventId: { + userId: session.user.id, + eventId, + }, + }, + update: { + rating, + comment: comment || null, + }, + create: { + userId: session.user.id, + eventId, + rating, + comment: comment || null, + }, + }); + + return NextResponse.json({ success: true, feedback }); + } catch (error) { + console.error("Error saving feedback:", error); + return NextResponse.json( + { error: "Erreur lors de l'enregistrement du feedback" }, + { status: 500 } + ); + } +} + +export async function GET( + request: Request, + { params }: { params: Promise<{ eventId: string }> } +) { + try { + const session = await auth(); + if (!session?.user?.id) { + return NextResponse.json({ error: "Non authentifié" }, { status: 401 }); + } + + const { eventId } = await params; + + // Récupérer le feedback de l'utilisateur pour cet événement + const feedback = await prisma.eventFeedback.findUnique({ + where: { + userId_eventId: { + userId: session.user.id, + eventId, + }, + }, + include: { + event: { + select: { + id: true, + name: true, + date: true, + }, + }, + }, + }); + + return NextResponse.json({ feedback }); + } catch (error) { + console.error("Error fetching feedback:", error); + return NextResponse.json( + { error: "Erreur lors de la récupération du feedback" }, + { status: 500 } + ); + } +} diff --git a/app/events/page.tsx b/app/events/page.tsx index 824e88d..643a6ad 100644 --- a/app/events/page.tsx +++ b/app/events/page.tsx @@ -27,28 +27,19 @@ export default async function EventsPage() { const initialRegistrations: Record = {}; if (session?.user?.id) { - const upcomingEvents = events.filter( - (e) => calculateEventStatus(e.date) === "UPCOMING" - ); - const eventIds = upcomingEvents.map((e) => e.id); + // Récupérer toutes les inscriptions (passées et à venir) pour permettre le feedback + const allRegistrations = await prisma.eventRegistration.findMany({ + where: { + userId: session.user.id, + }, + select: { + eventId: true, + }, + }); - if (eventIds.length > 0) { - const registrations = await prisma.eventRegistration.findMany({ - where: { - userId: session.user.id, - eventId: { - in: eventIds, - }, - }, - select: { - eventId: true, - }, - }); - - registrations.forEach((reg) => { - initialRegistrations[reg.eventId] = true; - }); - } + allRegistrations.forEach((reg) => { + initialRegistrations[reg.eventId] = true; + }); } return ( diff --git a/app/feedback/[eventId]/page.tsx b/app/feedback/[eventId]/page.tsx new file mode 100644 index 0000000..667c61f --- /dev/null +++ b/app/feedback/[eventId]/page.tsx @@ -0,0 +1,259 @@ +"use client"; + +import { useState, useEffect, type FormEvent } from "react"; +import { useSession } from "next-auth/react"; +import { useRouter, useParams } from "next/navigation"; +import Navigation from "@/components/Navigation"; +import { useBackgroundImage } from "@/hooks/usePreferences"; + +interface Event { + id: string; + name: string; + date: string; + description: string; +} + +interface Feedback { + id: string; + rating: number; + comment: string | null; +} + +export default function FeedbackPage() { + const { status } = useSession(); + const router = useRouter(); + const params = useParams(); + const eventId = params?.eventId as string; + const backgroundImage = useBackgroundImage("home", "/got-2.jpg"); + + const [event, setEvent] = useState(null); + const [existingFeedback, setExistingFeedback] = useState(null); + const [loading, setLoading] = useState(true); + const [submitting, setSubmitting] = useState(false); + const [error, setError] = useState(""); + const [success, setSuccess] = useState(false); + + const [rating, setRating] = useState(0); + const [comment, setComment] = useState(""); + + const fetchEventAndFeedback = async () => { + try { + // Récupérer l'événement + const eventResponse = await fetch(`/api/events/${eventId}`); + if (!eventResponse.ok) { + setError("Événement introuvable"); + setLoading(false); + return; + } + const eventData = await eventResponse.json(); + setEvent(eventData); + + // Récupérer le feedback existant si disponible + const feedbackResponse = await fetch(`/api/feedback/${eventId}`); + if (feedbackResponse.ok) { + const feedbackData = await feedbackResponse.json(); + if (feedbackData.feedback) { + setExistingFeedback(feedbackData.feedback); + setRating(feedbackData.feedback.rating); + setComment(feedbackData.feedback.comment || ""); + } + } + } catch { + setError("Erreur lors du chargement des données"); + } finally { + setLoading(false); + } + }; + + useEffect(() => { + if (status === "unauthenticated") { + router.push(`/login?redirect=/feedback/${eventId}`); + return; + } + + if (status === "authenticated" && eventId) { + fetchEventAndFeedback(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [status, eventId, router]); + + const handleSubmit = async (e: FormEvent) => { + e.preventDefault(); + setError(""); + setSuccess(false); + + if (rating === 0) { + setError("Veuillez sélectionner une note"); + return; + } + + setSubmitting(true); + + try { + const response = await fetch(`/api/feedback/${eventId}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + rating, + comment: comment.trim() || null, + }), + }); + + const data = await response.json(); + + if (!response.ok) { + setError(data.error || "Erreur lors de l'enregistrement"); + return; + } + + setSuccess(true); + setExistingFeedback(data.feedback); + + // Rediriger après 2 secondes + setTimeout(() => { + router.push("/events"); + }, 2000); + } catch { + setError("Erreur lors de l'enregistrement"); + } finally { + setSubmitting(false); + } + }; + + if (status === "loading" || loading) { + return ( +
+ +
+
Chargement...
+
+
+ ); + } + + if (!event) { + return ( +
+ +
+
Événement introuvable
+
+
+ ); + } + + return ( +
+ +
+ {/* Background Image */} +
+
+
+ + {/* Feedback Form */} +
+
+

+ + FEEDBACK + +

+

+ {existingFeedback + ? "Modifier votre feedback pour" + : "Donnez votre avis sur"} +

+

+ {event.name} +

+ + {success && ( +
+ Feedback enregistré avec succès ! Redirection... +
+ )} + + {error && ( +
+ {error} +
+ )} + +
+ {/* Rating */} +
+ +
+ {[1, 2, 3, 4, 5].map((star) => ( + + ))} +
+

+ {rating > 0 && `${rating}/5`} +

+
+ + {/* Comment */} +
+ +