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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user