|
| 1 | +import { useMemo, useState } from "react"; |
1 | 2 | import { Card } from "../../ui/card"; |
2 | 3 | import { Button } from "../../ui/button"; |
3 | 4 | import { Challenge } from "../../../interfaces/challenge.interface"; |
4 | | -import { deleteChallenge } from "../../../services/requests/challenge.service"; |
| 5 | +import { deleteChallenge, validateChallenge } from "../../../services/requests/challenge.service"; |
| 6 | +import { Trash2, Edit, CheckCircle2, Search } from "lucide-react"; |
| 7 | +import Select, { SingleValue } from "react-select"; |
| 8 | +import { Team } from "../../../interfaces/team.interface"; |
| 9 | +import { Faction } from "../../../interfaces/faction.interface"; |
| 10 | +import { User } from "../../../interfaces/user.interface"; |
5 | 11 | import Swal from "sweetalert2"; |
6 | | -import { Trash2, Edit } from "lucide-react"; |
| 12 | +import { Input } from "../../ui/input"; |
7 | 13 |
|
8 | 14 | interface Props { |
9 | 15 | challenges: Challenge[]; |
10 | 16 | refreshChallenges: () => void; |
11 | 17 | onEdit: (c: Challenge) => void; |
| 18 | + teams: Team[]; |
| 19 | + factions: Faction[]; |
| 20 | + users: User[]; |
12 | 21 | } |
13 | 22 |
|
14 | | -const AdminChallengeList = ({ challenges, refreshChallenges, onEdit }: Props) => { |
| 23 | +type ValidationTarget = "user" | "team" | "faction"; |
| 24 | + |
| 25 | +const AdminChallengeList = ({ challenges, refreshChallenges, onEdit, teams, factions, users }: Props) => { |
| 26 | + const [showValidationFormForId, setShowValidationFormForId] = useState<number | null>(null); |
| 27 | + const [validationType, setValidationType] = useState<ValidationTarget | null>(null); |
| 28 | + const [selectedTargetId, setSelectedTargetId] = useState<number | null>(null); |
| 29 | + const [searchTerm, setSearchTerm] = useState(""); |
| 30 | + |
| 31 | + |
| 32 | + const filteredChallenges = useMemo(() => { |
| 33 | + return challenges.filter( |
| 34 | + (c) => |
| 35 | + c.title.toLowerCase().includes(searchTerm.toLowerCase()) || |
| 36 | + c.description.toLowerCase().includes(searchTerm.toLowerCase()) || |
| 37 | + c.category.toLowerCase().includes(searchTerm.toLowerCase()) |
| 38 | + ); |
| 39 | + }, [challenges, searchTerm]); |
| 40 | + |
15 | 41 | const handleDelete = async (id: number) => { |
16 | 42 | const confirm = await Swal.fire({ |
| 43 | + title: "Supprimer ce challenge ?", |
| 44 | + text: "Cette action est irréversible 🚨", |
17 | 45 | icon: "warning", |
18 | | - title: "Supprimer ?", |
19 | | - text: "Cette action est irréversible", |
20 | 46 | showCancelButton: true, |
| 47 | + confirmButtonColor: "#e3342f", |
| 48 | + cancelButtonColor: "#6b7280", |
21 | 49 | confirmButtonText: "Oui, supprimer", |
22 | 50 | cancelButtonText: "Annuler", |
23 | 51 | }); |
24 | 52 |
|
25 | 53 | if (!confirm.isConfirmed) return; |
26 | 54 |
|
27 | | - await deleteChallenge(id); |
28 | | - Swal.fire({ icon: "success", title: "Challenge supprimé !" }); |
29 | | - refreshChallenges(); |
| 55 | + try { |
| 56 | + await deleteChallenge(id); |
| 57 | + Swal.fire("Supprimé ✅", "Le challenge a bien été supprimé.", "success"); |
| 58 | + refreshChallenges(); |
| 59 | + } catch (err) { |
| 60 | + Swal.fire("Erreur ❌", "Impossible de supprimer le challenge.", "error"); |
| 61 | + } |
| 62 | + }; |
| 63 | + |
| 64 | + const handleValidate = async () => { |
| 65 | + if (!showValidationFormForId || !validationType || !selectedTargetId) return; |
| 66 | + |
| 67 | + try { |
| 68 | + const res = await validateChallenge({ |
| 69 | + challengeId: showValidationFormForId, |
| 70 | + type: validationType, |
| 71 | + targetId: selectedTargetId, |
| 72 | + }); |
| 73 | + |
| 74 | + Swal.fire({ |
| 75 | + icon: "success", |
| 76 | + title: "Challenge validé ✅", |
| 77 | + text: res.message, |
| 78 | + timer: 2000, |
| 79 | + showConfirmButton: false, |
| 80 | + }); |
| 81 | + |
| 82 | + setShowValidationFormForId(null); |
| 83 | + setValidationType(null); |
| 84 | + setSelectedTargetId(null); |
| 85 | + refreshChallenges(); |
| 86 | + } catch (err) { |
| 87 | + console.error("Erreur lors de la validation du challenge", err); |
| 88 | + Swal.fire({ |
| 89 | + icon: "error", |
| 90 | + title: "Erreur ❌", |
| 91 | + text: "Impossible de valider ce challenge. Réessaie plus tard.", |
| 92 | + }); |
| 93 | + } |
30 | 94 | }; |
31 | 95 |
|
32 | 96 | return ( |
33 | 97 | <Card className="p-6 rounded-2xl shadow-lg"> |
34 | 98 | <h3 className="text-2xl font-semibold text-center mb-6">📜 Challenges</h3> |
35 | | - <div className="grid grid-cols-1 md:grid-cols-2 gap-6"> |
36 | | - {challenges.map((c) => ( |
37 | | - <div |
38 | | - key={c.id} |
39 | | - className="bg-gray-100 p-4 rounded-xl border shadow flex flex-col justify-between" |
40 | | - > |
41 | | - <div> |
42 | | - <h4 className="font-bold text-lg">{c.title}</h4> |
43 | | - <p className="text-gray-700">{c.description}</p> |
44 | | - <p className="text-sm text-gray-500 mt-1">Catégorie : {c.category}</p> |
45 | | - <p className="text-sm text-gray-500">Points : {c.points}</p> |
46 | | - </div> |
47 | 99 |
|
48 | | - <div className="flex gap-2 mt-4"> |
49 | | - <Button |
50 | | - onClick={() => onEdit(c)} |
51 | | - className="bg-yellow-600 hover:bg-yellow-700 text-white flex items-center gap-2" |
52 | | - > |
53 | | - <Edit className="w-4 h-4" /> Modifier |
54 | | - </Button> |
55 | | - <Button |
56 | | - onClick={() => handleDelete(c.id)} |
57 | | - className="bg-red-600 hover:bg-red-700 text-white flex items-center gap-2" |
58 | | - > |
59 | | - <Trash2 className="w-4 h-4" /> Supprimer |
60 | | - </Button> |
| 100 | + {/* 🔎 Barre de recherche */} |
| 101 | + <div className="flex items-center gap-3 mb-6"> |
| 102 | + <Search className="w-5 h-5 text-gray-500" /> |
| 103 | + <Input |
| 104 | + type="text" |
| 105 | + placeholder="Rechercher un challenge..." |
| 106 | + value={searchTerm} |
| 107 | + onChange={(e) => setSearchTerm(e.target.value)} |
| 108 | + className="flex-1" |
| 109 | + /> |
| 110 | + </div> |
| 111 | + |
| 112 | + {/* Liste filtrée */} |
| 113 | + <div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"> |
| 114 | + {filteredChallenges.length > 0 ? ( |
| 115 | + filteredChallenges.map((c) => ( |
| 116 | + <div |
| 117 | + key={c.id} |
| 118 | + className="bg-gray-100 p-4 rounded-xl border shadow flex flex-col justify-between" |
| 119 | + > |
| 120 | + <div> |
| 121 | + <h4 className="font-bold text-lg">{c.title}</h4> |
| 122 | + <p className="text-gray-700">{c.description}</p> |
| 123 | + <p className="text-sm text-gray-500 mt-1">Catégorie : {c.category}</p> |
| 124 | + <p className="text-sm text-gray-500">Points : {c.points}</p> |
| 125 | + </div> |
| 126 | + |
| 127 | + <div className="flex flex-wrap gap-2 mt-4"> |
| 128 | + <Button |
| 129 | + onClick={() => onEdit(c)} |
| 130 | + className="bg-yellow-600 hover:bg-yellow-700 text-white flex items-center gap-2" |
| 131 | + > |
| 132 | + <Edit className="w-4 h-4" /> Modifier |
| 133 | + </Button> |
| 134 | + <Button |
| 135 | + onClick={() => handleDelete(c.id)} |
| 136 | + className="bg-red-600 hover:bg-red-700 text-white flex items-center gap-2" |
| 137 | + > |
| 138 | + <Trash2 className="w-4 h-4" /> Supprimer |
| 139 | + </Button> |
| 140 | + <Button |
| 141 | + onClick={() => setShowValidationFormForId(c.id)} |
| 142 | + className="bg-blue-600 hover:bg-blue-700 text-white flex items-center gap-2" |
| 143 | + > |
| 144 | + <CheckCircle2 className="w-4 h-4" /> Valider |
| 145 | + </Button> |
| 146 | + </div> |
| 147 | + |
| 148 | + {showValidationFormForId === c.id && ( |
| 149 | + <div className="mt-6 bg-white p-4 border rounded-xl shadow-inner space-y-4"> |
| 150 | + <h4 className="font-bold text-lg">✅ Valider le challenge</h4> |
| 151 | + |
| 152 | + <Select |
| 153 | + placeholder="Choisir le type de cible" |
| 154 | + onChange={(option: SingleValue<{ value: ValidationTarget; label: string }>) => { |
| 155 | + setValidationType(option?.value ?? null); |
| 156 | + setSelectedTargetId(null); |
| 157 | + }} |
| 158 | + options={[ |
| 159 | + { value: "user", label: "Utilisateur" }, |
| 160 | + { value: "team", label: "Équipe" }, |
| 161 | + { value: "faction", label: "Faction" }, |
| 162 | + ]} |
| 163 | + /> |
| 164 | + |
| 165 | + {validationType === "user" && ( |
| 166 | + <Select |
| 167 | + placeholder="Sélectionner un utilisateur" |
| 168 | + onChange={(option) => setSelectedTargetId(Number(option?.value))} |
| 169 | + options={users.map((u: User) => ({ |
| 170 | + value: u.userId, |
| 171 | + label: `${u.firstName} ${u.lastName}`, |
| 172 | + }))} |
| 173 | + /> |
| 174 | + )} |
| 175 | + |
| 176 | + {validationType === "team" && ( |
| 177 | + <Select |
| 178 | + placeholder="Sélectionner une équipe" |
| 179 | + onChange={(option) => setSelectedTargetId(Number(option?.value))} |
| 180 | + options={teams.map((t: Team) => ({ |
| 181 | + value: t.teamId, |
| 182 | + label: t.name, |
| 183 | + }))} |
| 184 | + /> |
| 185 | + )} |
| 186 | + |
| 187 | + {validationType === "faction" && ( |
| 188 | + <Select |
| 189 | + placeholder="Sélectionner une faction" |
| 190 | + onChange={(option) => setSelectedTargetId(Number(option?.value))} |
| 191 | + options={factions.map((f: Faction) => ({ |
| 192 | + value: f.factionId, |
| 193 | + label: f.name, |
| 194 | + }))} |
| 195 | + /> |
| 196 | + )} |
| 197 | + |
| 198 | + <div className="flex gap-4"> |
| 199 | + <Button |
| 200 | + onClick={handleValidate} |
| 201 | + className="bg-blue-600 hover:bg-blue-700 text-white" |
| 202 | + > |
| 203 | + ✅ Valider |
| 204 | + </Button> |
| 205 | + <Button |
| 206 | + onClick={() => { |
| 207 | + setShowValidationFormForId(null); |
| 208 | + setValidationType(null); |
| 209 | + setSelectedTargetId(null); |
| 210 | + }} |
| 211 | + className="bg-gray-400 hover:bg-gray-500 text-white" |
| 212 | + > |
| 213 | + ❌ Annuler |
| 214 | + </Button> |
| 215 | + </div> |
| 216 | + </div> |
| 217 | + )} |
61 | 218 | </div> |
62 | | - </div> |
63 | | - ))} |
| 219 | + )) |
| 220 | + ) : ( |
| 221 | + <p className="text-center text-gray-500 col-span-full">Aucun challenge trouvé.</p> |
| 222 | + )} |
64 | 223 | </div> |
65 | 224 | </Card> |
66 | 225 | ); |
|
0 commit comments