Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions frontend/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"semi": true,
"trailingComma": "all",
"singleQuote": true,
"printWidth": 120,
"tabWidth": 4,
"arrowParens": "always",
"endOfLine": "lf",
"bracketSameLine": true
}
121 changes: 60 additions & 61 deletions frontend/src/components/Admin/AdminChallenge/adminChalengeList.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { CheckCircle2, Edit, Search, Trash2 } from "lucide-react";
import { useMemo, useState } from "react";
import Select, { type SingleValue } from "react-select";
import Swal from "sweetalert2";

import { type Challenge } from "../../../interfaces/challenge.interface";
import { type Faction } from "../../../interfaces/faction.interface";
import { type Team } from "../../../interfaces/team.interface";
import { type User } from "../../../interfaces/user.interface";
import { deleteChallenge, validateChallenge } from "../../../services/requests/challenge.service";
import { Button } from "../../ui/button";
import { Card, CardContent, CardHeader, CardTitle } from "../../ui/card";
import { Input } from "../../ui/input";
import { CheckCircle2, Edit, Search, Trash2 } from 'lucide-react';
import { useMemo, useState } from 'react';
import Select, { type SingleValue } from 'react-select';
import Swal from 'sweetalert2';

import { type Challenge } from '../../../interfaces/challenge.interface';
import { type Faction } from '../../../interfaces/faction.interface';
import { type Team } from '../../../interfaces/team.interface';
import { type User } from '../../../interfaces/user.interface';
import { deleteChallenge, validateChallenge } from '../../../services/requests/challenge.service';
import { Button } from '../../ui/button';
import { Card, CardContent, CardHeader, CardTitle } from '../../ui/card';
import { Input } from '../../ui/input';

interface Props {
challenges: Challenge[];
Expand All @@ -21,44 +21,43 @@ interface Props {
users: User[];
}

type ValidationTarget = "user" | "team" | "faction";
type ValidationTarget = 'user' | 'team' | 'faction';

const AdminChallengeList = ({ challenges, refreshChallenges, onEdit, teams, factions, users }: Props) => {
const [showValidationFormForId, setShowValidationFormForId] = useState<number | null>(null);
const [validationType, setValidationType] = useState<ValidationTarget | null>(null);
const [selectedTargetId, setSelectedTargetId] = useState<number | null>(null);
const [searchTerm, setSearchTerm] = useState("");

const [searchTerm, setSearchTerm] = useState('');

const filteredChallenges = useMemo(() => {
return challenges.filter(
(c) =>
c.title.toLowerCase().includes(searchTerm.toLowerCase()) ||
c.description.toLowerCase().includes(searchTerm.toLowerCase()) ||
c.category.toLowerCase().includes(searchTerm.toLowerCase())
c.category.toLowerCase().includes(searchTerm.toLowerCase()),
);
}, [challenges, searchTerm]);

const handleDelete = async (id: number) => {
const confirm = await Swal.fire({
title: "Supprimer ce challenge ?",
text: "Cette action est irréversible 🚨",
icon: "warning",
title: 'Supprimer ce challenge ?',
text: 'Cette action est irréversible 🚨',
icon: 'warning',
showCancelButton: true,
confirmButtonColor: "#e3342f",
cancelButtonColor: "#6b7280",
confirmButtonText: "Oui, supprimer",
cancelButtonText: "Annuler",
confirmButtonColor: '#e3342f',
cancelButtonColor: '#6b7280',
confirmButtonText: 'Oui, supprimer',
cancelButtonText: 'Annuler',
});

if (!confirm.isConfirmed) return;

try {
await deleteChallenge(id);
Swal.fire("Supprimé ✅", "Le challenge a bien été supprimé.", "success");
Swal.fire('Supprimé ✅', 'Le challenge a bien été supprimé.', 'success');
refreshChallenges();
} catch {
Swal.fire("Erreur ❌", "Impossible de supprimer le challenge.", "error");
Swal.fire('Erreur ❌', 'Impossible de supprimer le challenge.', 'error');
}
};

Expand All @@ -73,8 +72,8 @@ const AdminChallengeList = ({ challenges, refreshChallenges, onEdit, teams, fact
});

Swal.fire({
icon: "success",
title: "Challenge validé ✅",
icon: 'success',
title: 'Challenge validé ✅',
text: res.message,
timer: 2000,
showConfirmButton: false,
Expand All @@ -85,24 +84,21 @@ const AdminChallengeList = ({ challenges, refreshChallenges, onEdit, teams, fact
setSelectedTargetId(null);
refreshChallenges();
} catch (err) {
console.error("Erreur lors de la validation du challenge", err);
console.error('Erreur lors de la validation du challenge', err);
Swal.fire({
icon: "error",
title: "Erreur ❌",
text: "Impossible de valider ce challenge. Réessaie plus tard.",
icon: 'error',
title: 'Erreur ❌',
text: 'Impossible de valider ce challenge. Réessaie plus tard.',
});
}
};

return (
<Card className="w-full max-w-3xl mx-auto">
<Card className="w-full max-w-7xl mx-auto">
<CardHeader>
<CardTitle className="text-2xl font-semibold text-gray-800 text-center">
📜 Challenges
</CardTitle>
<CardTitle className="text-2xl font-semibold text-gray-800 text-center">📜 Challenges</CardTitle>
</CardHeader>
<CardContent>

{/* 🔎 Barre de recherche */}
<div className="flex items-center gap-3 mb-6">
<Search className="w-5 h-5 text-gray-500" />
Expand All @@ -119,10 +115,7 @@ const AdminChallengeList = ({ challenges, refreshChallenges, onEdit, teams, fact
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{filteredChallenges.length > 0 ? (
filteredChallenges.map((c) => (
<Card
key={c.id}
className="bg-gray-100 border shadow flex flex-col justify-between"
>
<Card key={c.id} className="bg-gray-100 border shadow flex flex-col justify-between">
<CardContent className="p-4">
<div>
<h4 className="font-bold text-lg">{c.title}</h4>
Expand All @@ -134,20 +127,17 @@ const AdminChallengeList = ({ challenges, refreshChallenges, onEdit, teams, fact
<div className="flex flex-wrap gap-2 mt-4">
<Button
onClick={() => onEdit(c)}
className="bg-yellow-600 hover:bg-yellow-700 text-white flex items-center gap-2"
>
className="bg-yellow-600 hover:bg-yellow-700 text-white flex items-center gap-2">
<Edit className="w-4 h-4" /> Modifier
</Button>
<Button
onClick={() => handleDelete(c.id)}
className="bg-red-600 hover:bg-red-700 text-white flex items-center gap-2"
>
className="bg-red-600 hover:bg-red-700 text-white flex items-center gap-2">
<Trash2 className="w-4 h-4" /> Supprimer
</Button>
<Button
onClick={() => setShowValidationFormForId(c.id)}
className="bg-blue-600 hover:bg-blue-700 text-white flex items-center gap-2"
>
className="bg-blue-600 hover:bg-blue-700 text-white flex items-center gap-2">
<CheckCircle2 className="w-4 h-4" /> Valider
</Button>
</div>
Expand All @@ -159,43 +149,54 @@ const AdminChallengeList = ({ challenges, refreshChallenges, onEdit, teams, fact

<Select
placeholder="Choisir le type de cible"
onChange={(option: SingleValue<{ value: ValidationTarget; label: string }>) => {
onChange={(
option: SingleValue<{
value: ValidationTarget;
label: string;
}>,
) => {
setValidationType(option?.value ?? null);
setSelectedTargetId(null);
}}
options={[
{ value: "user", label: "Utilisateur" },
{ value: "team", label: "Équipe" },
{ value: "faction", label: "Faction" },
{ value: 'user', label: 'Utilisateur' },
{ value: 'team', label: 'Équipe' },
{ value: 'faction', label: 'Faction' },
]}
/>

{validationType === "user" && (
{validationType === 'user' && (
<Select
placeholder="Sélectionner un utilisateur"
onChange={(option) => setSelectedTargetId(Number(option?.value))}
onChange={(option) =>
setSelectedTargetId(Number(option?.value))
}
options={users.map((u: User) => ({
value: u.userId,
label: `${u.firstName} ${u.lastName}`,
}))}
/>
)}

{validationType === "team" && (
{validationType === 'team' && (
<Select
placeholder="Sélectionner une équipe"
onChange={(option) => setSelectedTargetId(Number(option?.value))}
onChange={(option) =>
setSelectedTargetId(Number(option?.value))
}
options={teams.map((t: Team) => ({
value: t.teamId,
label: t.name,
}))}
/>
)}

{validationType === "faction" && (
{validationType === 'faction' && (
<Select
placeholder="Sélectionner une faction"
onChange={(option) => setSelectedTargetId(Number(option?.value))}
onChange={(option) =>
setSelectedTargetId(Number(option?.value))
}
options={factions.map((f: Faction) => ({
value: f.factionId,
label: f.name,
Expand All @@ -206,8 +207,7 @@ const AdminChallengeList = ({ challenges, refreshChallenges, onEdit, teams, fact
<div className="flex gap-4">
<Button
onClick={handleValidate}
className="bg-blue-600 hover:bg-blue-700 text-white"
>
className="bg-blue-600 hover:bg-blue-700 text-white">
✅ Valider
</Button>
<Button
Expand All @@ -216,8 +216,7 @@ const AdminChallengeList = ({ challenges, refreshChallenges, onEdit, teams, fact
setValidationType(null);
setSelectedTargetId(null);
}}
className="bg-gray-400 hover:bg-gray-500 text-white"
>
className="bg-gray-400 hover:bg-gray-500 text-white">
❌ Annuler
</Button>
</div>
Expand Down
Loading
Loading