Add feedback management features: Implement functions to add bonus points and mark feedback as read in the FeedbackManagement component. Update EventFeedback model to include isRead property, enhancing user interaction and feedback tracking.

This commit is contained in:
Julien Froidefond
2025-12-16 16:43:53 +01:00
parent 3dd82c2bd4
commit 16e4b63ffd
10 changed files with 387 additions and 16 deletions

View File

@@ -1,11 +1,18 @@
"use client";
import { useState, useEffect } from "react";
import {
addFeedbackBonusPoints,
markFeedbackAsRead,
} from "@/actions/admin/feedback";
import { Button } from "@/components/ui";
import Avatar from "@/components/ui/Avatar";
interface Feedback {
id: string;
rating: number;
comment: string | null;
isRead: boolean;
createdAt: string;
event: {
id: string;
@@ -17,6 +24,8 @@ interface Feedback {
id: string;
username: string;
email: string;
avatar: string | null;
score: number;
};
}
@@ -35,6 +44,10 @@ export default function FeedbackManagement() {
const [loading, setLoading] = useState(true);
const [error, setError] = useState("");
const [selectedEvent, setSelectedEvent] = useState<string | null>(null);
const [addingPoints, setAddingPoints] = useState<Record<string, boolean>>(
{}
);
const [markingRead, setMarkingRead] = useState<Record<string, boolean>>({});
useEffect(() => {
fetchFeedbacks();
@@ -92,9 +105,59 @@ export default function FeedbackManagement() {
);
};
const filteredFeedbacks = selectedEvent
const handleAddPoints = async (userId: string, points: number) => {
const key = `${userId}-${points}`;
setAddingPoints((prev) => ({ ...prev, [key]: true }));
setError("");
try {
const result = await addFeedbackBonusPoints(userId, points);
if (result.success) {
// Rafraîchir les données pour voir les nouveaux scores
await fetchFeedbacks();
// Rafraîchir le score dans le header si l'utilisateur est connecté
window.dispatchEvent(new Event("refreshUserScore"));
} else {
setError(result.error || "Erreur lors de l'ajout des points");
}
} catch {
setError("Erreur lors de l'ajout des points");
} finally {
setAddingPoints((prev) => ({ ...prev, [key]: false }));
}
};
const handleMarkAsRead = async (feedbackId: string, isRead: boolean) => {
setMarkingRead((prev) => ({ ...prev, [feedbackId]: true }));
setError("");
try {
const result = await markFeedbackAsRead(feedbackId, isRead);
if (result.success) {
// Rafraîchir les données pour voir le nouveau statut
await fetchFeedbacks();
} else {
setError(result.error || "Erreur lors de la mise à jour");
}
} catch {
setError("Erreur lors de la mise à jour");
} finally {
setMarkingRead((prev) => ({ ...prev, [feedbackId]: false }));
}
};
const filteredFeedbacks = (selectedEvent
? feedbacks.filter((f) => f.event.id === selectedEvent)
: feedbacks;
: feedbacks
).sort((a, b) => {
// Trier : non lus en premier, puis par date décroissante
if (a.isRead !== b.isRead) {
return a.isRead ? 1 : -1;
}
return (
new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
);
});
if (loading) {
return (
@@ -184,20 +247,45 @@ export default function FeedbackManagement() {
{filteredFeedbacks.map((feedback) => (
<div
key={feedback.id}
className="bg-black/40 border border-pixel-gold/20 rounded p-3 sm:p-4"
className={`bg-black/40 border rounded p-3 sm:p-4 ${
feedback.isRead
? "border-pixel-gold/20 opacity-75"
: "border-pixel-gold/50 bg-pixel-gold/5"
}`}
>
<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>
{/* En-tête utilisateur avec avatar */}
<div className="flex items-center gap-2 sm:gap-3 mb-3">
<Avatar
src={feedback.user.avatar}
username={feedback.user.username}
size="md"
borderClassName="border-pixel-gold/30"
/>
<div className="flex-1 min-w-0">
<div className="flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-2 mb-1">
<h4 className="text-white font-semibold text-sm sm:text-base break-words">
{feedback.user.username}
</h4>
<span className="text-pixel-gold font-bold text-xs sm:text-sm">
{feedback.user.score.toLocaleString("fr-FR")} pts
</span>
</div>
<span className="text-gray-500 text-[10px] sm:text-xs break-all">
{feedback.user.email}
</span>
</div>
</div>
<div className="text-pixel-gold text-xs sm:text-sm font-semibold mb-2 break-words">
{feedback.event.name}
<div className="flex items-center gap-2 mb-2">
<div className="text-pixel-gold text-xs sm:text-sm font-semibold break-words">
{feedback.event.name}
</div>
{!feedback.isRead && (
<span className="bg-pixel-gold/20 text-pixel-gold text-[10px] px-1.5 py-0.5 rounded uppercase font-semibold">
Non lu
</span>
)}
</div>
<div className="text-gray-500 text-[10px] sm:text-xs mb-2">
{new Date(feedback.createdAt).toLocaleDateString(
@@ -212,8 +300,23 @@ export default function FeedbackManagement() {
)}
</div>
</div>
<div className="flex-shrink-0">
<div className="flex flex-col items-end gap-2 flex-shrink-0">
{renderStars(feedback.rating)}
<Button
variant={feedback.isRead ? "secondary" : "success"}
size="sm"
onClick={() =>
handleMarkAsRead(feedback.id, !feedback.isRead)
}
disabled={markingRead[feedback.id]}
className="text-xs whitespace-nowrap"
>
{markingRead[feedback.id]
? "..."
: feedback.isRead
? "Marquer non lu"
: "Marquer lu"}
</Button>
</div>
</div>
{feedback.comment && (
@@ -223,6 +326,39 @@ export default function FeedbackManagement() {
</p>
</div>
)}
{/* Boutons pour ajouter des points bonus */}
<div className="mt-3 pt-3 border-t border-pixel-gold/20 flex flex-wrap gap-2">
<span className="text-gray-400 text-xs sm:text-sm mr-2">
Points bonus:
</span>
<Button
variant="primary"
size="sm"
onClick={() => handleAddPoints(feedback.user.id, 10)}
disabled={addingPoints[`${feedback.user.id}-10`]}
className="text-xs"
>
{addingPoints[`${feedback.user.id}-10`] ? "..." : "+10"}
</Button>
<Button
variant="primary"
size="sm"
onClick={() => handleAddPoints(feedback.user.id, 100)}
disabled={addingPoints[`${feedback.user.id}-100`]}
className="text-xs"
>
{addingPoints[`${feedback.user.id}-100`] ? "..." : "+100"}
</Button>
<Button
variant="primary"
size="sm"
onClick={() => handleAddPoints(feedback.user.id, 1000)}
disabled={addingPoints[`${feedback.user.id}-1000`]}
className="text-xs"
>
{addingPoints[`${feedback.user.id}-1000`] ? "..." : "+1000"}
</Button>
</div>
</div>
))}
</div>