Skip to content
Merged
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: 7 additions & 3 deletions backend/src/controllers/tent.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
try {
await tent_service.createTent(userId1, userId2);
Ok(res, { msg: 'Tente réservée avec succès.' });
} catch (err: any) {

Check warning on line 20 in backend/src/controllers/tent.controller.ts

View workflow job for this annotation

GitHub Actions / lint-api

Unexpected any. Specify a different type
Error(res, { msg: err.message || 'Erreur lors de la création de la tente.' });
}
};
Expand Down Expand Up @@ -89,18 +89,22 @@
const emailOptions = {
from: email_from,
to: [user1.email, user2.email],
subject: confirmed ? '🎉 Votre tente a été validée !' : '⛺ Votre tente a été dévalidée',
text: '', // optionnel
subject: confirmed
? "🎉 Votre tente a été validée !"
: "⛺ Votre tente a été invalidée",
text: "", // optionnel
html: htmlEmail,
};

// Envoi
await sendEmail(emailOptions);

Ok(res, {
msg: confirmed ? 'Tente validée et email envoyé.' : 'Tente dévalidée et email envoyé.',
msg: confirmed
? "Tente validée et email envoyé."
: "Tente invalidée et email envoyé.",
});
} catch (err: any) {

Check warning on line 107 in backend/src/controllers/tent.controller.ts

View workflow job for this annotation

GitHub Actions / lint-api

Unexpected any. Specify a different type
console.error(err);
Error(res, {
msg: err.message || "Erreur lors de la mise à jour ou de l'envoi d'email.",
Expand Down
26 changes: 24 additions & 2 deletions backend/src/services/challenge.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,29 @@
throw new Error("Type de challenge non valide");
}

// 3. Créer l'objet de validation du challenge
// 3. Vérifier si ce challenge a déjà été validé pour cette cible
const targetColumn =
type === "user"
? challengeValidationSchema.target_user_id
: type === "team"
? challengeValidationSchema.target_team_id
: challengeValidationSchema.target_faction_id;

const existingValidation = await db
.select()
.from(challengeValidationSchema)
.where(
and(
eq(challengeValidationSchema.challenge_id, challengeId),
eq(targetColumn, targetId)
)
);

if (existingValidation.length > 0) {
throw new Error("Ce challenge a déjà été validé pour cette cible");
}

// 4. Créer l'objet de validation du challenge
const newChallengeValidationPoints = {
challenge_id: challengeId,
validated_by_admin_id: validatedBy,
Expand All @@ -94,7 +116,7 @@
target_faction_id: target_faction_id,
};

// 4. Insérer la validation du challenge dans la base de données
// 5. Insérer la validation du challenge dans la base de données
const inserted = await db.insert(challengeValidationSchema).values(newChallengeValidationPoints).returning();

return inserted[0];
Expand Down Expand Up @@ -149,7 +171,7 @@
if (!challenge[0]) throw new Error("Challenge introuvable");

// 2. Construire l'objet de mise à jour
const updateValues: any = {};

Check warning on line 174 in backend/src/services/challenge.service.ts

View workflow job for this annotation

GitHub Actions / lint-api

Unexpected any. Specify a different type
if (title) updateValues.title = title;
if (description) updateValues.description = description;
if (category) updateValues.category = category;
Expand Down
2 changes: 1 addition & 1 deletion backend/src/services/tent.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,5 +131,5 @@ export const toggleTentConfirmation = async (
)
);

return { success: true, message: confirmed ? "Tente validée." : "Tente dévalidée." };
return { success: true, message: confirmed ? "Tente validée." : "Tente invalidée." };
};
91 changes: 40 additions & 51 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 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 @@ -26,8 +26,9 @@ 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 [selectedTargetIds, setSelectedTargetIds] = useState<number[]>([]);
const [searchTerm, setSearchTerm] = useState("");


const filteredChallenges = useMemo(() => {
return challenges.filter(
Expand Down Expand Up @@ -61,14 +62,20 @@ const AdminChallengeList = ({ challenges, refreshChallenges, onEdit, teams, fact
}
};

const handleValidate = async () => {
if (!showValidationFormForId || !validationType || !selectedTargetId) return;
const handleValidationCLick = async (c: Challenge) => {
setShowValidationFormForId(c.id);
setValidationType(c.category.toLowerCase() as ValidationTarget);
}

const handleValidate = async () => {
if (!showValidationFormForId || !validationType || selectedTargetIds.length === 0) return;

for (const targetId of selectedTargetIds) {
try {
const res = await validateChallenge({
challengeId: showValidationFormForId,
type: validationType,
targetId: selectedTargetId,
targetId: targetId,
});

Swal.fire({
Expand All @@ -81,16 +88,17 @@ const AdminChallengeList = ({ challenges, refreshChallenges, onEdit, teams, fact

setShowValidationFormForId(null);
setValidationType(null);
setSelectedTargetId(null);
setSelectedTargetIds([]);
refreshChallenges();
} catch (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: err as string,
});
}
}
};

return (
Expand Down Expand Up @@ -136,8 +144,9 @@ const AdminChallengeList = ({ challenges, refreshChallenges, onEdit, teams, fact
<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">
onClick={() => {handleValidationCLick(c)}}
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 @@ -147,30 +156,12 @@ const AdminChallengeList = ({ challenges, refreshChallenges, onEdit, teams, fact
<CardContent className="p-4 space-y-4">
<h4 className="font-bold text-lg">✅ Valider le challenge</h4>

<Select
placeholder="Choisir le type de cible"
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' },
]}
/>

{validationType === 'user' && (
<Select
isMulti
placeholder="Sélectionner un utilisateur"
onChange={(option) =>
setSelectedTargetId(Number(option?.value))
}
onChange={(options) => setSelectedTargetIds(options.map((o) => Number(o.value)))}
options={users.map((u: User) => ({
value: u.userId,
label: `${u.firstName} ${u.lastName}`,
Expand All @@ -180,10 +171,9 @@ const AdminChallengeList = ({ challenges, refreshChallenges, onEdit, teams, fact

{validationType === 'team' && (
<Select
isMulti
placeholder="Sélectionner une équipe"
onChange={(option) =>
setSelectedTargetId(Number(option?.value))
}
onChange={(options) => setSelectedTargetIds(options.map((o) => Number(o.value)))}
options={teams.map((t: Team) => ({
value: t.teamId,
label: t.name,
Expand All @@ -193,10 +183,9 @@ const AdminChallengeList = ({ challenges, refreshChallenges, onEdit, teams, fact

{validationType === 'faction' && (
<Select
isMulti
placeholder="Sélectionner une faction"
onChange={(option) =>
setSelectedTargetId(Number(option?.value))
}
onChange={(options) => setSelectedTargetIds(options.map((o) => Number(o.value)))}
options={factions.map((f: Faction) => ({
value: f.factionId,
label: f.name,
Expand All @@ -214,7 +203,7 @@ const AdminChallengeList = ({ challenges, refreshChallenges, onEdit, teams, fact
onClick={() => {
setShowValidationFormForId(null);
setValidationType(null);
setSelectedTargetId(null);
setSelectedTargetIds([]);
}}
className="bg-gray-400 hover:bg-gray-500 text-white">
❌ Annuler
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,19 @@ const ChallengeEditor = ({ editingChallenge, setEditingChallenge, refreshChallen
setEditingChallenge(null);
};

const resetFormPostCreation = () => {
setForm({ title: "", description: "", category: form.category, points: 0 });
setEditingChallenge(null);
};


const handleSubmit = async () => {
setLoading(true);
try {
if (!form.title || !form.description || !form.category) {
Swal.fire({ icon: "error", title: "Tous les champs doivent être remplis" });
return;
}
if (editingChallenge) {
await updateChallenge({ id: editingChallenge.id, ...form });
Swal.fire({ icon: 'success', title: 'Challenge mis à jour !' });
Expand All @@ -53,7 +63,7 @@ const ChallengeEditor = ({ editingChallenge, setEditingChallenge, refreshChallen
Swal.fire({ icon: 'success', title: 'Challenge créé !' });
}
refreshChallenges();
resetForm();
resetFormPostCreation();
} catch {
Swal.fire({ icon: 'error', title: "Erreur lors de l'enregistrement" });
} finally {
Expand Down
Loading
Loading