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
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 4m36s
This commit is contained in:
233
components/admin/FeedbackManagement.tsx
Normal file
233
components/admin/FeedbackManagement.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user