diff --git a/app/api/admin/events/[id]/registrations/route.ts b/app/api/admin/events/[id]/registrations/route.ts
new file mode 100644
index 0000000..c150446
--- /dev/null
+++ b/app/api/admin/events/[id]/registrations/route.ts
@@ -0,0 +1,31 @@
+import { NextResponse } from "next/server";
+import { auth } from "@/lib/auth";
+import { eventRegistrationService } from "@/services/events/event-registration.service";
+import { Role } from "@/prisma/generated/prisma/client";
+
+export async function GET(
+ request: Request,
+ { params }: { params: Promise<{ id: string }> }
+) {
+ try {
+ const session = await auth();
+
+ if (!session?.user || session.user.role !== Role.ADMIN) {
+ return NextResponse.json({ error: "Accès refusé" }, { status: 403 });
+ }
+
+ const { id: eventId } = await params;
+ const registrations = await eventRegistrationService.getEventRegistrations(
+ eventId
+ );
+
+ return NextResponse.json(registrations);
+ } catch (error) {
+ console.error("Error fetching event registrations:", error);
+ return NextResponse.json(
+ { error: "Erreur lors de la récupération des inscrits" },
+ { status: 500 }
+ );
+ }
+}
+
diff --git a/components/admin/ChallengeManagement.tsx b/components/admin/ChallengeManagement.tsx
index d9717a4..4a2fb1b 100644
--- a/components/admin/ChallengeManagement.tsx
+++ b/components/admin/ChallengeManagement.tsx
@@ -9,7 +9,15 @@ import {
adminCancelChallenge,
reactivateChallenge,
} from "@/actions/admin/challenges";
-import { Button, Card, Input, Textarea, Alert, Modal, CloseButton } from "@/components/ui";
+import {
+ Button,
+ Card,
+ Input,
+ Textarea,
+ Alert,
+ Modal,
+ CloseButton,
+} from "@/components/ui";
import { Avatar } from "@/components/ui";
interface Challenge {
@@ -441,115 +449,115 @@ export default function ChallengeManagement() {
/>
-
-
- {selectedChallenge.title}
-
-
- {selectedChallenge.description}
-
+
+
+ {selectedChallenge.title}
+
+
+ {selectedChallenge.description}
+
-
-
-
-
- {selectedChallenge.challenger.username}
-
-
-
VS
-
-
-
- {selectedChallenge.challenged.username}
-
-
+
+
+
+
+ {selectedChallenge.challenger.username}
+
+
+
VS
+
+
+
+ {selectedChallenge.challenged.username}
+
+
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
)}
@@ -582,67 +590,67 @@ export default function ChallengeManagement() {
/>
-
)}
diff --git a/components/admin/EventManagement.tsx b/components/admin/EventManagement.tsx
index 80e8649..f26962f 100644
--- a/components/admin/EventManagement.tsx
+++ b/components/admin/EventManagement.tsx
@@ -11,7 +11,9 @@ import {
Badge,
Modal,
CloseButton,
+ Avatar,
} from "@/components/ui";
+import { updateUser } from "@/actions/admin/users";
interface Event {
id: string;
@@ -28,6 +30,24 @@ interface Event {
registrationsCount?: number;
}
+interface EventRegistration {
+ id: string;
+ userId: string;
+ eventId: string;
+ createdAt: string;
+ user: {
+ id: string;
+ username: string;
+ avatar: string | null;
+ score: number;
+ level: number;
+ hp: number;
+ maxHp: number;
+ xp: number;
+ maxXp: number;
+ };
+}
+
interface EventFormData {
date: string;
name: string;
@@ -78,6 +98,14 @@ export default function EventManagement() {
const [editingEvent, setEditingEvent] = useState(null);
const [isCreating, setIsCreating] = useState(false);
const [saving, setSaving] = useState(false);
+ const [viewingRegistrations, setViewingRegistrations] =
+ useState(null);
+ const [registrations, setRegistrations] = useState([]);
+ const [loadingRegistrations, setLoadingRegistrations] = useState(false);
+ const [editingScores, setEditingScores] = useState>(
+ {}
+ );
+ const [savingScore, setSavingScore] = useState(null);
const [formData, setFormData] = useState({
date: "",
name: "",
@@ -207,6 +235,75 @@ export default function EventManagement() {
});
};
+ const handleViewRegistrations = async (event: Event) => {
+ setViewingRegistrations(event);
+ setLoadingRegistrations(true);
+ try {
+ const response = await fetch(
+ `/api/admin/events/${event.id}/registrations`
+ );
+ if (response.ok) {
+ const data = await response.json();
+ setRegistrations(data);
+ // Initialiser les scores d'édition avec les scores actuels
+ const scoresMap: Record = {};
+ data.forEach((reg: EventRegistration) => {
+ scoresMap[reg.user.id] = reg.user.score;
+ });
+ setEditingScores(scoresMap);
+ } else {
+ alert("Erreur lors de la récupération des inscrits");
+ }
+ } catch (error) {
+ console.error("Error fetching registrations:", error);
+ alert("Erreur lors de la récupération des inscrits");
+ } finally {
+ setLoadingRegistrations(false);
+ }
+ };
+
+ const handleCloseRegistrations = () => {
+ setViewingRegistrations(null);
+ setRegistrations([]);
+ setEditingScores({});
+ };
+
+ const handleScoreChange = (userId: string, newScore: number) => {
+ setEditingScores({
+ ...editingScores,
+ [userId]: newScore,
+ });
+ };
+
+ const handleSaveScore = async (userId: string) => {
+ const newScore = editingScores[userId];
+ if (newScore === undefined) return;
+
+ setSavingScore(userId);
+ startTransition(async () => {
+ try {
+ const result = await updateUser(userId, { score: newScore });
+ if (result.success) {
+ // Mettre à jour le score dans la liste locale
+ setRegistrations((prev) =>
+ prev.map((reg) =>
+ reg.user.id === userId
+ ? { ...reg, user: { ...reg.user, score: newScore } }
+ : reg
+ )
+ );
+ } else {
+ alert(result.error || "Erreur lors de la mise à jour du score");
+ }
+ } catch (error) {
+ console.error("Error updating score:", error);
+ alert("Erreur lors de la mise à jour du score");
+ } finally {
+ setSavingScore(null);
+ }
+ });
+ };
+
if (loading) {
return Chargement...
;
}
@@ -410,7 +507,15 @@ export default function EventManagement() {
{!isCreating && !editingEvent && (
-
+
+
)}
+
+ {/* Modal des inscrits */}
+ {viewingRegistrations && (
+
+
+
+
+ Inscrits à "{viewingRegistrations.name}"
+
+
+
+
+ {loadingRegistrations ? (
+
+ Chargement...
+
+ ) : registrations.length === 0 ? (
+
+ Aucun inscrit pour cet événement
+
+ ) : (
+
+ {registrations.map((registration) => {
+ const user = registration.user;
+ const currentScore = editingScores[user.id] ?? user.score;
+ const isSaving = savingScore === user.id;
+
+ return (
+
+
+
+
+
+
+ {user.username}
+
+
+ Niveau {user.level} • HP: {user.hp}/{user.maxHp} •
+ XP: {user.xp}/{user.maxXp}
+
+
+
+
+
+
+
+ handleScoreChange(
+ user.id,
+ parseInt(e.target.value) || 0
+ )
+ }
+ disabled={isSaving}
+ className="w-24 px-2 sm:px-3 py-1 bg-black/60 border border-pixel-gold/30 rounded text-white text-xs sm:text-sm text-center disabled:opacity-50"
+ />
+
+
+
+
+
+
+
+
+
+ );
+ })}
+
+ )}
+
+
+ )}
);
}
diff --git a/services/events/event-registration.service.ts b/services/events/event-registration.service.ts
index 04825b5..ecfe7c4 100644
--- a/services/events/event-registration.service.ts
+++ b/services/events/event-registration.service.ts
@@ -38,7 +38,8 @@ export class EventRegistrationService {
}
// Récupérer les points à retirer depuis les préférences du site
- const sitePreferences = await sitePreferencesService.getOrCreateSitePreferences();
+ const sitePreferences =
+ await sitePreferencesService.getOrCreateSitePreferences();
const pointsToRemove = sitePreferences.eventRegistrationPoints || 100;
// Supprimer l'inscription et retirer les points en parallèle
@@ -118,6 +119,35 @@ export class EventRegistrationService {
return count;
}
+ /**
+ * Récupère tous les inscrits d'un événement avec leurs informations
+ */
+ async getEventRegistrations(eventId: string) {
+ return prisma.eventRegistration.findMany({
+ where: {
+ eventId,
+ },
+ include: {
+ user: {
+ select: {
+ id: true,
+ username: true,
+ avatar: true,
+ score: true,
+ level: true,
+ hp: true,
+ maxHp: true,
+ xp: true,
+ maxXp: true,
+ },
+ },
+ },
+ orderBy: {
+ createdAt: "asc",
+ },
+ });
+ }
+
/**
* Valide et inscrit un utilisateur à un événement avec toutes les règles métier
*/
@@ -146,7 +176,8 @@ export class EventRegistrationService {
}
// Récupérer les points à attribuer depuis les préférences du site
- const sitePreferences = await sitePreferencesService.getOrCreateSitePreferences();
+ const sitePreferences =
+ await sitePreferencesService.getOrCreateSitePreferences();
const pointsToAward = sitePreferences.eventRegistrationPoints || 100;
// Créer l'inscription et attribuer les points en parallèle