Refactor component imports and structure: Update import paths for various components to improve organization, moving them into appropriate subdirectories. Remove unused components related to user and event management, enhancing code clarity and maintainability across the application.
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 4m36s

This commit is contained in:
Julien Froidefond
2025-12-12 16:48:41 +01:00
parent 880e96d6e4
commit 97db800c73
27 changed files with 23 additions and 23 deletions

View File

@@ -0,0 +1,233 @@
"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-0.5 sm:gap-1">
{[1, 2, 3, 4, 5].map((star) => (
<span
key={star}
className={`text-sm sm:text-lg ${
star <= rating ? "text-pixel-gold" : "text-gray-600"
}`}
>
</span>
))}
<span className="text-gray-400 text-xs sm:text-sm ml-1 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-4 sm:p-8">
<p className="text-gray-400 text-center text-sm">Chargement...</p>
</div>
);
}
return (
<div className="space-y-4 sm:space-y-6">
{/* Statistiques par événement */}
{statistics.length > 0 && (
<div className="bg-black/60 border border-pixel-gold/30 rounded-lg p-3 sm:p-6">
<h3 className="text-pixel-gold font-bold text-base sm:text-lg mb-4 break-words">
Statistiques par événement
</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-4">
{statistics.map((stat) => (
<div
key={stat.eventId}
className={`bg-black/40 border rounded p-3 sm: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 flex-col sm:flex-row sm:items-start sm:justify-between gap-2 mb-2">
<h4 className="text-white font-semibold text-xs sm:text-sm break-words">
{stat.eventName}
</h4>
<span className="text-pixel-gold text-[10px] sm:text-xs uppercase whitespace-nowrap flex-shrink-0">
{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-[10px] sm:text-xs">
Moyenne: {stat.averageRating.toFixed(2)}/5
</div>
<div className="text-gray-400 text-[10px] sm:text-xs">
{stat.feedbackCount} feedback
{stat.feedbackCount > 1 ? "s" : ""}
</div>
</div>
))}
</div>
{selectedEvent && (
<button
onClick={() => setSelectedEvent(null)}
className="mt-4 text-pixel-gold text-xs sm: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-3 sm:p-6">
<h3 className="text-pixel-gold font-bold text-base sm:text-lg mb-4 break-words">
{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-3 sm:px-4 py-2 sm:py-3 rounded text-xs sm:text-sm mb-4">
{error}
</div>
)}
{filteredFeedbacks.length === 0 ? (
<p className="text-gray-400 text-center py-8 text-sm">
Aucun feedback pour le moment
</p>
) : (
<div className="space-y-3 sm:space-y-4">
{filteredFeedbacks.map((feedback) => (
<div
key={feedback.id}
className="bg-black/40 border border-pixel-gold/20 rounded p-3 sm:p-4"
>
<div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-3 mb-3">
<div className="flex-1 min-w-0">
<div className="flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-3 mb-2">
<h4 className="text-white font-semibold text-sm sm:text-base break-words">
{feedback.user.username}
</h4>
<span className="text-gray-500 text-[10px] sm:text-xs break-all">
{feedback.user.email}
</span>
</div>
<div className="text-pixel-gold text-xs sm:text-sm font-semibold mb-2 break-words">
{feedback.event.name}
</div>
<div className="text-gray-500 text-[10px] sm: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 className="flex-shrink-0">
{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-xs sm:text-sm whitespace-pre-wrap break-words">
{feedback.comment}
</p>
</div>
)}
</div>
))}
</div>
)}
</div>
</div>
);
}