Add admin challenge management features: Implement functions for canceling and reactivating challenges, enhance error handling, and update the ChallengeManagement component to support these actions. Update API to retrieve all challenge statuses for admin and improve UI to display active challenges count.
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 6m16s
All checks were successful
Deploy with Docker Compose / deploy (push) Successful in 6m16s
This commit is contained in:
@@ -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 (
|
||||
<div className="text-center text-pixel-gold py-8">Chargement...</div>
|
||||
@@ -185,15 +225,18 @@ export default function ChallengeManagement() {
|
||||
}
|
||||
|
||||
if (challenges.length === 0) {
|
||||
return (
|
||||
<div className="text-center text-gray-400 py-8">
|
||||
Aucun défi en attente
|
||||
</div>
|
||||
);
|
||||
return <div className="text-center text-gray-400 py-8">Aucun défi</div>;
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="space-y-4">
|
||||
@@ -208,15 +251,41 @@ export default function ChallengeManagement() {
|
||||
</Alert>
|
||||
)}
|
||||
<div className="text-sm text-gray-400 mb-4">
|
||||
{acceptedChallenges.length} défi
|
||||
{acceptedChallenges.length > 1 ? "s" : ""} en attente de validation
|
||||
{acceptedChallenges.length > 0 && (
|
||||
<span>
|
||||
{acceptedChallenges.length} défi
|
||||
{acceptedChallenges.length > 1 ? "s" : ""} en attente de désignation
|
||||
du gagnant
|
||||
</span>
|
||||
)}
|
||||
{pendingChallenges.length > 0 && (
|
||||
<span className="ml-2">
|
||||
<span className={acceptedChallenges.length > 0 ? "ml-2" : ""}>
|
||||
• {pendingChallenges.length} défi
|
||||
{pendingChallenges.length > 1 ? "s" : ""} en attente
|
||||
d'acceptation
|
||||
</span>
|
||||
)}
|
||||
{cancelledChallenges.length > 0 && (
|
||||
<span className="ml-2">
|
||||
• {cancelledChallenges.length} défi
|
||||
{cancelledChallenges.length > 1 ? "s" : ""} annulé
|
||||
{cancelledChallenges.length > 1 ? "s" : ""}
|
||||
</span>
|
||||
)}
|
||||
{completedChallenges.length > 0 && (
|
||||
<span className="ml-2">
|
||||
• {completedChallenges.length} défi
|
||||
{completedChallenges.length > 1 ? "s" : ""} complété
|
||||
{completedChallenges.length > 1 ? "s" : ""}
|
||||
</span>
|
||||
)}
|
||||
{rejectedChallenges.length > 0 && (
|
||||
<span className="ml-2">
|
||||
• {rejectedChallenges.length} défi
|
||||
{rejectedChallenges.length > 1 ? "s" : ""} rejeté
|
||||
{rejectedChallenges.length > 1 ? "s" : ""}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{challenges.map((challenge) => (
|
||||
@@ -260,13 +329,27 @@ export default function ChallengeManagement() {
|
||||
<span
|
||||
className={`px-2 py-1 rounded ${
|
||||
challenge.status === "ACCEPTED"
|
||||
? "bg-green-500/20 text-green-400"
|
||||
: "bg-yellow-500/20 text-yellow-400"
|
||||
? "bg-blue-500/20 text-blue-400"
|
||||
: challenge.status === "COMPLETED"
|
||||
? "bg-green-500/20 text-green-400"
|
||||
: challenge.status === "CANCELLED"
|
||||
? "bg-gray-500/20 text-gray-400"
|
||||
: challenge.status === "REJECTED"
|
||||
? "bg-red-500/20 text-red-400"
|
||||
: "bg-yellow-500/20 text-yellow-400"
|
||||
}`}
|
||||
>
|
||||
{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}
|
||||
</span>
|
||||
</div>
|
||||
{challenge.acceptedAt && (
|
||||
@@ -291,7 +374,28 @@ export default function ChallengeManagement() {
|
||||
variant="primary"
|
||||
size="sm"
|
||||
>
|
||||
Valider/Rejeter
|
||||
Désigner le gagnant
|
||||
</Button>
|
||||
)}
|
||||
{challenge.status !== "CANCELLED" &&
|
||||
challenge.status !== "COMPLETED" && (
|
||||
<Button
|
||||
onClick={() => handleCancel(challenge.id)}
|
||||
variant="secondary"
|
||||
size="sm"
|
||||
disabled={isPending}
|
||||
>
|
||||
Annuler
|
||||
</Button>
|
||||
)}
|
||||
{challenge.status === "CANCELLED" && (
|
||||
<Button
|
||||
onClick={() => handleReactivate(challenge.id)}
|
||||
variant="primary"
|
||||
size="sm"
|
||||
disabled={isPending}
|
||||
>
|
||||
Réactiver
|
||||
</Button>
|
||||
)}
|
||||
<Button
|
||||
@@ -328,7 +432,7 @@ export default function ChallengeManagement() {
|
||||
>
|
||||
<div className="p-6">
|
||||
<h2 className="text-2xl font-bold text-pixel-gold mb-4">
|
||||
Valider/Rejeter le défi
|
||||
Désigner le gagnant
|
||||
</h2>
|
||||
|
||||
<div className="mb-6">
|
||||
@@ -418,7 +522,7 @@ export default function ChallengeManagement() {
|
||||
disabled={!winnerId || isPending}
|
||||
className="flex-1"
|
||||
>
|
||||
{isPending ? "Validation..." : "Valider le défi"}
|
||||
{isPending ? "Enregistrement..." : "Confirmer le gagnant"}
|
||||
</Button>
|
||||
<Button
|
||||
onClick={handleReject}
|
||||
|
||||
Reference in New Issue
Block a user