Implement event feedback functionality: Add EventFeedback model to Prisma schema, enabling users to submit ratings and comments for events. Update EventsPageSection and AdminPanel components to support feedback management, including UI for submitting feedback and viewing existing feedbacks. Refactor registration logic to retrieve all user registrations for improved feedback handling.
This commit is contained in:
229
components/FeedbackManagement.tsx
Normal file
229
components/FeedbackManagement.tsx
Normal file
@@ -0,0 +1,229 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
|
||||
interface Feedback {
|
||||
id: string;
|
||||
rating: number;
|
||||
comment: string | null;
|
||||
createdAt: string;
|
||||
event: {
|
||||
id: string;
|
||||
name: string;
|
||||
date: string;
|
||||
type: string;
|
||||
};
|
||||
user: {
|
||||
id: string;
|
||||
username: string;
|
||||
email: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface EventStatistics {
|
||||
eventId: string;
|
||||
eventName: string;
|
||||
eventDate: string | null;
|
||||
eventType: string | null;
|
||||
averageRating: number;
|
||||
feedbackCount: number;
|
||||
}
|
||||
|
||||
export default function FeedbackManagement() {
|
||||
const [feedbacks, setFeedbacks] = useState<Feedback[]>([]);
|
||||
const [statistics, setStatistics] = useState<EventStatistics[]>([]);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState("");
|
||||
const [selectedEvent, setSelectedEvent] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
fetchFeedbacks();
|
||||
}, []);
|
||||
|
||||
const fetchFeedbacks = async () => {
|
||||
try {
|
||||
const response = await fetch("/api/admin/feedback");
|
||||
if (!response.ok) {
|
||||
setError("Erreur lors du chargement des feedbacks");
|
||||
return;
|
||||
}
|
||||
const data = await response.json();
|
||||
setFeedbacks(data.feedbacks || []);
|
||||
setStatistics(data.statistics || []);
|
||||
} catch {
|
||||
setError("Erreur lors du chargement des feedbacks");
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const getEventTypeLabel = (type: string) => {
|
||||
switch (type) {
|
||||
case "ATELIER":
|
||||
return "Atelier";
|
||||
case "KATA":
|
||||
return "Kata";
|
||||
case "PRESENTATION":
|
||||
return "Présentation";
|
||||
case "LEARNING_HOUR":
|
||||
return "Learning Hour";
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
};
|
||||
|
||||
const renderStars = (rating: number) => {
|
||||
return (
|
||||
<div className="flex items-center gap-1">
|
||||
{[1, 2, 3, 4, 5].map((star) => (
|
||||
<span
|
||||
key={star}
|
||||
className={`text-lg ${
|
||||
star <= rating ? "text-pixel-gold" : "text-gray-600"
|
||||
}`}
|
||||
>
|
||||
★
|
||||
</span>
|
||||
))}
|
||||
<span className="text-gray-400 text-sm ml-2">({rating}/5)</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const filteredFeedbacks = selectedEvent
|
||||
? feedbacks.filter((f) => f.event.id === selectedEvent)
|
||||
: feedbacks;
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="bg-black/60 border border-pixel-gold/30 rounded-lg p-8">
|
||||
<p className="text-gray-400 text-center">Chargement...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-6">
|
||||
{/* Statistiques par événement */}
|
||||
{statistics.length > 0 && (
|
||||
<div className="bg-black/60 border border-pixel-gold/30 rounded-lg p-6">
|
||||
<h3 className="text-pixel-gold font-bold text-lg mb-4">
|
||||
Statistiques par événement
|
||||
</h3>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{statistics.map((stat) => (
|
||||
<div
|
||||
key={stat.eventId}
|
||||
className={`bg-black/40 border rounded p-4 cursor-pointer transition ${
|
||||
selectedEvent === stat.eventId
|
||||
? "border-pixel-gold bg-pixel-gold/10"
|
||||
: "border-pixel-gold/30 hover:border-pixel-gold/50"
|
||||
}`}
|
||||
onClick={() =>
|
||||
setSelectedEvent(
|
||||
selectedEvent === stat.eventId ? null : stat.eventId
|
||||
)
|
||||
}
|
||||
>
|
||||
<div className="flex items-start justify-between mb-2">
|
||||
<h4 className="text-white font-semibold text-sm">
|
||||
{stat.eventName}
|
||||
</h4>
|
||||
<span className="text-pixel-gold text-xs uppercase">
|
||||
{stat.eventType && getEventTypeLabel(stat.eventType)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 mb-2">
|
||||
{renderStars(Math.round(stat.averageRating))}
|
||||
</div>
|
||||
<div className="text-gray-400 text-xs">
|
||||
Moyenne: {stat.averageRating.toFixed(2)}/5
|
||||
</div>
|
||||
<div className="text-gray-400 text-xs">
|
||||
{stat.feedbackCount} feedback
|
||||
{stat.feedbackCount > 1 ? "s" : ""}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
{selectedEvent && (
|
||||
<button
|
||||
onClick={() => setSelectedEvent(null)}
|
||||
className="mt-4 text-pixel-gold text-sm hover:text-orange-400 transition"
|
||||
>
|
||||
Voir tous les feedbacks
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Liste des feedbacks */}
|
||||
<div className="bg-black/60 border border-pixel-gold/30 rounded-lg p-6">
|
||||
<h3 className="text-pixel-gold font-bold text-lg mb-4">
|
||||
{selectedEvent
|
||||
? `Feedbacks pour: ${
|
||||
statistics.find((s) => s.eventId === selectedEvent)?.eventName
|
||||
}`
|
||||
: "Tous les feedbacks"}
|
||||
</h3>
|
||||
|
||||
{error && (
|
||||
<div className="bg-red-900/50 border border-red-500/50 text-red-400 px-4 py-3 rounded text-sm mb-4">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{filteredFeedbacks.length === 0 ? (
|
||||
<p className="text-gray-400 text-center py-8">
|
||||
Aucun feedback pour le moment
|
||||
</p>
|
||||
) : (
|
||||
<div className="space-y-4">
|
||||
{filteredFeedbacks.map((feedback) => (
|
||||
<div
|
||||
key={feedback.id}
|
||||
className="bg-black/40 border border-pixel-gold/20 rounded p-4"
|
||||
>
|
||||
<div className="flex items-start justify-between mb-3">
|
||||
<div className="flex-1">
|
||||
<div className="flex items-center gap-3 mb-2">
|
||||
<h4 className="text-white font-semibold">
|
||||
{feedback.user.username}
|
||||
</h4>
|
||||
<span className="text-gray-500 text-xs">
|
||||
{feedback.user.email}
|
||||
</span>
|
||||
</div>
|
||||
<div className="text-pixel-gold text-sm font-semibold mb-2">
|
||||
{feedback.event.name}
|
||||
</div>
|
||||
<div className="text-gray-500 text-xs mb-2">
|
||||
{new Date(feedback.createdAt).toLocaleDateString(
|
||||
"fr-FR",
|
||||
{
|
||||
day: "numeric",
|
||||
month: "long",
|
||||
year: "numeric",
|
||||
hour: "2-digit",
|
||||
minute: "2-digit",
|
||||
}
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div>{renderStars(feedback.rating)}</div>
|
||||
</div>
|
||||
{feedback.comment && (
|
||||
<div className="mt-3 pt-3 border-t border-pixel-gold/20">
|
||||
<p className="text-gray-300 text-sm whitespace-pre-wrap">
|
||||
{feedback.comment}
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user