230 lines
7.1 KiB
TypeScript
230 lines
7.1 KiB
TypeScript
"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>
|
|
);
|
|
}
|