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" && ( + )}