diff --git a/actions/admin/challenges.ts b/actions/admin/challenges.ts
index 6fbafc5..dcf32c5 100644
--- a/actions/admin/challenges.ts
+++ b/actions/admin/challenges.ts
@@ -166,3 +166,71 @@ export async function deleteChallenge(challengeId: string) {
};
}
}
+
+export async function adminCancelChallenge(challengeId: string) {
+ try {
+ await checkAdminAccess();
+
+ const challenge = await challengeService.adminCancelChallenge(challengeId);
+
+ revalidatePath("/admin");
+ revalidatePath("/challenges");
+
+ return {
+ success: true,
+ message: "Défi annulé avec succès",
+ data: challenge,
+ };
+ } catch (error) {
+ console.error("Admin cancel challenge error:", error);
+
+ if (error instanceof ValidationError) {
+ return { success: false, error: error.message };
+ }
+ if (error instanceof NotFoundError) {
+ return { success: false, error: error.message };
+ }
+ if (error instanceof Error && error.message.includes("Accès refusé")) {
+ return { success: false, error: error.message };
+ }
+
+ return {
+ success: false,
+ error: "Une erreur est survenue lors de l'annulation du défi",
+ };
+ }
+}
+
+export async function reactivateChallenge(challengeId: string) {
+ try {
+ await checkAdminAccess();
+
+ const challenge = await challengeService.reactivateChallenge(challengeId);
+
+ revalidatePath("/admin");
+ revalidatePath("/challenges");
+
+ return {
+ success: true,
+ message: "Défi réactivé avec succès",
+ data: challenge,
+ };
+ } catch (error) {
+ console.error("Reactivate challenge error:", error);
+
+ if (error instanceof ValidationError) {
+ return { success: false, error: error.message };
+ }
+ if (error instanceof NotFoundError) {
+ return { success: false, error: error.message };
+ }
+ if (error instanceof Error && error.message.includes("Accès refusé")) {
+ return { success: false, error: error.message };
+ }
+
+ return {
+ success: false,
+ error: "Une erreur est survenue lors de la réactivation du défi",
+ };
+ }
+}
diff --git a/app/api/admin/challenges/route.ts b/app/api/admin/challenges/route.ts
index 5c4316b..4411522 100644
--- a/app/api/admin/challenges/route.ts
+++ b/app/api/admin/challenges/route.ts
@@ -11,12 +11,8 @@ export async function GET() {
return NextResponse.json({ error: "Accès refusé" }, { status: 403 });
}
- // Récupérer tous les défis (PENDING et ACCEPTED) pour l'admin
- const allChallenges = await challengeService.getAllChallenges();
- // Filtrer pour ne garder que PENDING et ACCEPTED
- const challenges = allChallenges.filter(
- (c) => c.status === "PENDING" || c.status === "ACCEPTED"
- );
+ // Récupérer tous les défis pour l'admin (PENDING, ACCEPTED, CANCELLED, COMPLETED, REJECTED)
+ const challenges = await challengeService.getAllChallenges();
return NextResponse.json(challenges);
} catch (error) {
diff --git a/app/api/challenges/active-count/route.ts b/app/api/challenges/active-count/route.ts
new file mode 100644
index 0000000..871e717
--- /dev/null
+++ b/app/api/challenges/active-count/route.ts
@@ -0,0 +1,23 @@
+import { NextResponse } from "next/server";
+import { auth } from "@/lib/auth";
+import { challengeService } from "@/services/challenges/challenge.service";
+
+export async function GET() {
+ try {
+ const session = await auth();
+
+ if (!session?.user?.id) {
+ return NextResponse.json({ count: 0 });
+ }
+
+ const count = await challengeService.getActiveChallengesCount(
+ session.user.id
+ );
+
+ return NextResponse.json({ count });
+ } catch (error) {
+ console.error("Error fetching active challenges count:", error);
+ return NextResponse.json({ count: 0 });
+ }
+}
+
diff --git a/components/admin/ChallengeManagement.tsx b/components/admin/ChallengeManagement.tsx
index c714369..98542a6 100644
--- a/components/admin/ChallengeManagement.tsx
+++ b/components/admin/ChallengeManagement.tsx
@@ -6,6 +6,8 @@ import {
rejectChallenge,
updateChallenge,
deleteChallenge,
+ adminCancelChallenge,
+ reactivateChallenge,
} from "@/actions/admin/challenges";
import { Button, Card, Input, Textarea, Alert } from "@/components/ui";
import { Avatar } from "@/components/ui";
@@ -178,6 +180,44 @@ export default function ChallengeManagement() {
});
};
+ const handleCancel = async (challengeId: string) => {
+ if (!confirm("Êtes-vous sûr de vouloir annuler ce défi ?")) {
+ return;
+ }
+
+ startTransition(async () => {
+ const result = await adminCancelChallenge(challengeId);
+
+ if (result.success) {
+ setSuccessMessage("Défi annulé avec succès");
+ fetchChallenges();
+ setTimeout(() => setSuccessMessage(null), 5000);
+ } else {
+ setErrorMessage(result.error || "Erreur lors de l'annulation");
+ setTimeout(() => setErrorMessage(null), 5000);
+ }
+ });
+ };
+
+ const handleReactivate = async (challengeId: string) => {
+ if (!confirm("Êtes-vous sûr de vouloir réactiver ce défi ?")) {
+ return;
+ }
+
+ startTransition(async () => {
+ const result = await reactivateChallenge(challengeId);
+
+ if (result.success) {
+ setSuccessMessage("Défi réactivé avec succès");
+ fetchChallenges();
+ setTimeout(() => setSuccessMessage(null), 5000);
+ } else {
+ setErrorMessage(result.error || "Erreur lors de la réactivation");
+ setTimeout(() => setErrorMessage(null), 5000);
+ }
+ });
+ };
+
if (loading) {
return (
Chargement...
@@ -185,15 +225,18 @@ export default function ChallengeManagement() {
}
if (challenges.length === 0) {
- return (
-
- Aucun défi en attente
-
- );
+ return Aucun défi
;
}
const acceptedChallenges = challenges.filter((c) => c.status === "ACCEPTED");
const pendingChallenges = challenges.filter((c) => c.status === "PENDING");
+ const cancelledChallenges = challenges.filter(
+ (c) => c.status === "CANCELLED"
+ );
+ const completedChallenges = challenges.filter(
+ (c) => c.status === "COMPLETED"
+ );
+ const rejectedChallenges = challenges.filter((c) => c.status === "REJECTED");
return (
@@ -208,15 +251,41 @@ export default function ChallengeManagement() {
)}
- {acceptedChallenges.length} défi
- {acceptedChallenges.length > 1 ? "s" : ""} en attente de validation
+ {acceptedChallenges.length > 0 && (
+
+ {acceptedChallenges.length} défi
+ {acceptedChallenges.length > 1 ? "s" : ""} en attente de désignation
+ du gagnant
+
+ )}
{pendingChallenges.length > 0 && (
-
+ 0 ? "ml-2" : ""}>
• {pendingChallenges.length} défi
{pendingChallenges.length > 1 ? "s" : ""} en attente
d'acceptation
)}
+ {cancelledChallenges.length > 0 && (
+
+ • {cancelledChallenges.length} défi
+ {cancelledChallenges.length > 1 ? "s" : ""} annulé
+ {cancelledChallenges.length > 1 ? "s" : ""}
+
+ )}
+ {completedChallenges.length > 0 && (
+
+ • {completedChallenges.length} défi
+ {completedChallenges.length > 1 ? "s" : ""} complété
+ {completedChallenges.length > 1 ? "s" : ""}
+
+ )}
+ {rejectedChallenges.length > 0 && (
+
+ • {rejectedChallenges.length} défi
+ {rejectedChallenges.length > 1 ? "s" : ""} rejeté
+ {rejectedChallenges.length > 1 ? "s" : ""}
+
+ )}
{challenges.map((challenge) => (
@@ -260,13 +329,27 @@ export default function ChallengeManagement() {
- {challenge.status === "ACCEPTED"
- ? "Accepté"
- : "En attente d'acceptation"}
+ {challenge.status === "PENDING"
+ ? "En attente d'acceptation"
+ : challenge.status === "ACCEPTED"
+ ? "En cours - En attente de désignation du gagnant"
+ : challenge.status === "COMPLETED"
+ ? "Complété"
+ : challenge.status === "CANCELLED"
+ ? "Annulé"
+ : challenge.status === "REJECTED"
+ ? "Rejeté"
+ : challenge.status}
{challenge.acceptedAt && (
@@ -291,7 +374,28 @@ export default function ChallengeManagement() {
variant="primary"
size="sm"
>
- Valider/Rejeter
+ Désigner le gagnant
+
+ )}
+ {challenge.status !== "CANCELLED" &&
+ challenge.status !== "COMPLETED" && (
+
+ )}
+ {challenge.status === "CANCELLED" && (
+
)}