|
<!DOCTYPE html> |
|
<html lang="fr"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>FIFA Tournament Pro</title> |
|
<script src="https://cdn.tailwindcss.com"></script> |
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> |
|
<style> |
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap'); |
|
|
|
body { |
|
font-family: 'Inter', sans-serif; |
|
transition: all 0.3s ease; |
|
} |
|
|
|
.tab-content { |
|
display: none; |
|
} |
|
|
|
.tab-content.active { |
|
display: block; |
|
animation: fadeIn 0.3s ease-out; |
|
} |
|
|
|
@keyframes fadeIn { |
|
from { opacity: 0; } |
|
to { opacity: 1; } |
|
} |
|
|
|
.toast { |
|
position: fixed; |
|
bottom: 20px; |
|
right: 20px; |
|
transform: translateY(100px); |
|
opacity: 0; |
|
transition: all 0.3s ease; |
|
z-index: 1000; |
|
} |
|
|
|
.toast.show { |
|
transform: translateY(0); |
|
opacity: 1; |
|
} |
|
|
|
.modal { |
|
display: none; |
|
position: fixed; |
|
top: 0; |
|
left: 0; |
|
width: 100%; |
|
height: 100%; |
|
background-color: rgba(0,0,0,0.5); |
|
z-index: 100; |
|
justify-content: center; |
|
align-items: center; |
|
} |
|
|
|
.modal.active { |
|
display: flex; |
|
animation: fadeIn 0.3s ease-out; |
|
} |
|
|
|
.modal-content { |
|
background-color: white; |
|
padding: 2rem; |
|
border-radius: 0.5rem; |
|
width: 90%; |
|
max-width: 500px; |
|
max-height: 90vh; |
|
overflow-y: auto; |
|
} |
|
|
|
.dark .modal-content { |
|
background-color: #1f2937; |
|
} |
|
|
|
.match-card { |
|
transition: all 0.2s ease; |
|
} |
|
|
|
.match-card:hover { |
|
transform: translateY(-2px); |
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); |
|
} |
|
|
|
.dark .match-card:hover { |
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.3); |
|
} |
|
|
|
.pronostic-correct { |
|
background-color: #d1fae5 !important; |
|
border-left: 4px solid #10b981; |
|
} |
|
|
|
.pronostic-incorrect { |
|
background-color: #fee2e2 !important; |
|
border-left: 4px solid #ef4444; |
|
} |
|
|
|
.phase-finale { |
|
border-left: 4px solid #f59e0b; |
|
} |
|
|
|
.winner-card { |
|
animation: pulse 2s infinite; |
|
border: 2px solid #f59e0b; |
|
} |
|
|
|
@keyframes pulse { |
|
0% { box-shadow: 0 0 0 0 rgba(245, 158, 11, 0.4); } |
|
70% { box-shadow: 0 0 0 10px rgba(245, 158, 11, 0); } |
|
100% { box-shadow: 0 0 0 0 rgba(245, 158, 11, 0); } |
|
} |
|
|
|
.goat-title { |
|
background: linear-gradient(45deg, #f59e0b, #ef4444, #3b82f6); |
|
background-size: 200% 200%; |
|
animation: gradient 3s ease infinite; |
|
-webkit-background-clip: text; |
|
background-clip: text; |
|
color: transparent; |
|
} |
|
|
|
@keyframes gradient { |
|
0% { background-position: 0% 50%; } |
|
50% { background-position: 100% 50%; } |
|
100% { background-position: 0% 50%; } |
|
} |
|
|
|
|
|
::-webkit-scrollbar { |
|
width: 8px; |
|
height: 8px; |
|
} |
|
|
|
::-webkit-scrollbar-track { |
|
background: #f1f1f1; |
|
} |
|
|
|
::-webkit-scrollbar-thumb { |
|
background: #888; |
|
border-radius: 4px; |
|
} |
|
|
|
::-webkit-scrollbar-thumb:hover { |
|
background: #555; |
|
} |
|
|
|
.dark ::-webkit-scrollbar-track { |
|
background: #374151; |
|
} |
|
|
|
.dark ::-webkit-scrollbar-thumb { |
|
background: #6b7280; |
|
} |
|
|
|
.dark ::-webkit-scrollbar-thumb:hover { |
|
background: #9ca3af; |
|
} |
|
|
|
|
|
.scrollbar-hide::-webkit-scrollbar { |
|
display: none; |
|
} |
|
|
|
.scrollbar-hide { |
|
-ms-overflow-style: none; |
|
scrollbar-width: none; |
|
} |
|
</style> |
|
</head> |
|
<body class="bg-gray-100 dark:bg-gray-900 text-gray-800 dark:text-gray-200 min-h-screen"> |
|
<div class="container mx-auto px-4 py-8"> |
|
|
|
<header class="flex justify-between items-center mb-8"> |
|
<div class="flex items-center gap-4"> |
|
<div class="w-12 h-12 bg-blue-500 rounded-lg flex items-center justify-center text-white"> |
|
<i class="fas fa-trophy text-xl"></i> |
|
</div> |
|
<h1 id="tournamentTitle" class="text-3xl font-bold bg-gradient-to-r from-blue-500 to-yellow-500 bg-clip-text text-transparent"> |
|
FIFA Tournament Pro |
|
</h1> |
|
</div> |
|
|
|
<div class="flex items-center gap-4"> |
|
<div class="text-sm bg-gray-200 dark:bg-gray-700 px-3 py-1 rounded-lg"> |
|
<i class="fas fa-calendar-day mr-2"></i> |
|
<span id="currentDate"></span> |
|
</div> |
|
|
|
<button id="themeToggle" class="w-10 h-10 rounded-full bg-gray-200 dark:bg-gray-700 flex items-center justify-center hover:bg-gray-300 dark:hover:bg-gray-600 transition"> |
|
<i class="fas fa-moon dark:hidden"></i> |
|
<i class="fas fa-sun hidden dark:block"></i> |
|
</button> |
|
</div> |
|
</header> |
|
|
|
|
|
<nav class="flex overflow-x-auto pb-2 mb-8 gap-2 scrollbar-hide"> |
|
<button class="tab-btn active px-4 py-2 rounded-lg font-medium bg-blue-500 text-white hover:bg-blue-600 transition" data-tab="dashboard"> |
|
<i class="fas fa-home mr-2"></i>Dashboard |
|
</button> |
|
<button class="tab-btn px-4 py-2 rounded-lg font-medium hover:bg-gray-200 dark:hover:bg-gray-700 transition" data-tab="players"> |
|
<i class="fas fa-users mr-2"></i>Joueurs |
|
</button> |
|
<button class="tab-btn px-4 py-2 rounded-lg font-medium hover:bg-gray-200 dark:hover:bg-gray-700 transition" data-tab="schedule"> |
|
<i class="fas fa-calendar-alt mr-2"></i>Calendrier |
|
</button> |
|
<button class="tab-btn px-4 py-2 rounded-lg font-medium hover:bg-gray-200 dark:hover:bg-gray-700 transition" data-tab="results"> |
|
<i class="fas fa-chart-bar mr-2"></i>Résultats |
|
</button> |
|
<button class="tab-btn px-4 py-2 rounded-lg font-medium hover:bg-gray-200 dark:hover:bg-gray-700 transition" data-tab="pronostics"> |
|
<i class="fas fa-binoculars mr-2"></i>Pronostics |
|
</button> |
|
<button class="tab-btn px-4 py-2 rounded-lg font-medium hover:bg-gray-200 dark:hover:bg-gray-700 transition" data-tab="settings"> |
|
<i class="fas fa-cog mr-2"></i>Paramètres |
|
</button> |
|
</nav> |
|
|
|
|
|
<div id="dashboard" class="tab-content active"> |
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6"> |
|
|
|
<div class="bg-white dark:bg-gray-800 p-6 rounded-xl shadow"> |
|
<h2 class="text-xl font-bold mb-4 flex items-center gap-2"> |
|
<i class="fas fa-chart-pie"></i> Statistiques |
|
</h2> |
|
<div class="grid grid-cols-2 gap-4"> |
|
<div class="bg-gray-100 dark:bg-gray-700 p-3 rounded-lg"> |
|
<div class="text-sm text-gray-500 dark:text-gray-400"> |
|
<i class="fas fa-users mr-1"></i> Joueurs |
|
</div> |
|
<div class="text-2xl font-bold" id="playerCount">0</div> |
|
</div> |
|
<div class="bg-gray-100 dark:bg-gray-700 p-3 rounded-lg"> |
|
<div class="text-sm text-gray-500 dark:text-gray-400"> |
|
<i class="fas fa-gamepad mr-1"></i> Matchs |
|
</div> |
|
<div class="text-2xl font-bold" id="matchCount">0</div> |
|
</div> |
|
<div class="bg-gray-100 dark:bg-gray-700 p-3 rounded-lg"> |
|
<div class="text-sm text-gray-500 dark:text-gray-400"> |
|
<i class="fas fa-check-circle mr-1"></i> Terminés |
|
</div> |
|
<div class="text-2xl font-bold" id="completedCount">0</div> |
|
</div> |
|
<div class="bg-gray-100 dark:bg-gray-700 p-3 rounded-lg"> |
|
<div class="text-sm text-gray-500 dark:text-gray-400"> |
|
<i class="fas fa-clock mr-1"></i> À venir |
|
</div> |
|
<div class="text-2xl font-bold" id="upcomingCount">0</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="bg-white dark:bg-gray-800 p-6 rounded-xl shadow"> |
|
<h2 class="text-xl font-bold mb-4 flex items-center gap-2"> |
|
<i class="fas fa-clock"></i> Prochain Match |
|
</h2> |
|
<div class="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg match-card" id="nextMatchCard"> |
|
<div class="text-center py-8 text-gray-500 dark:text-gray-400"> |
|
<i class="fas fa-calendar-plus text-4xl mb-2"></i> |
|
<p>Aucun match programmé</p> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="bg-white dark:bg-gray-800 p-6 rounded-xl shadow" id="winnerCardContainer"> |
|
<h2 class="text-xl font-bold mb-4 flex items-center gap-2"> |
|
<i class="fas fa-trophy"></i> Champion en titre |
|
</h2> |
|
<div class="bg-gray-100 dark:bg-gray-700 p-4 rounded-lg text-center" id="winnerCard"> |
|
<div class="py-8 text-gray-500 dark:text-gray-400"> |
|
<i class="fas fa-trophy text-4xl mb-2"></i> |
|
<p>Aucun vainqueur encore</p> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="bg-white dark:bg-gray-800 p-6 rounded-xl shadow mb-6"> |
|
<h2 class="text-xl font-bold mb-4 flex items-center gap-2"> |
|
<i class="fas fa-bolt"></i> Actions Rapides |
|
</h2> |
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-4"> |
|
<button class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg flex items-center justify-center gap-2 transition" data-tab-target="players"> |
|
<i class="fas fa-user-plus"></i> Ajouter Joueur |
|
</button> |
|
<button class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-lg flex items-center justify-center gap-2 transition" id="createMatchBtn"> |
|
<i class="fas fa-calendar-plus"></i> Créer Match |
|
</button> |
|
<button class="bg-purple-500 hover:bg-purple-600 text-white px-4 py-2 rounded-lg flex items-center justify-center gap-2 transition" data-tab-target="results"> |
|
<i class="fas fa-trophy"></i> Voir Classement |
|
</button> |
|
<button class="bg-yellow-500 hover:bg-yellow-600 text-white px-4 py-2 rounded-lg flex items-center justify-center gap-2 transition" id="generateTournamentBtn"> |
|
<i class="fas fa-random"></i> Générer Tournoi |
|
</button> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="bg-white dark:bg-gray-800 p-6 rounded-xl shadow"> |
|
<h2 class="text-xl font-bold mb-4 flex items-center gap-2"> |
|
<i class="fas fa-history"></i> Derniers Résultats |
|
</h2> |
|
<div class="space-y-3" id="recentResults"> |
|
|
|
<div class="text-center py-8 text-gray-500 dark:text-gray-400"> |
|
<i class="fas fa-info-circle text-4xl mb-2"></i> |
|
<p>Aucun résultat récent</p> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="players" class="tab-content"> |
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6"> |
|
|
|
<div class="bg-white dark:bg-gray-800 p-6 rounded-xl shadow"> |
|
<h2 class="text-xl font-bold mb-4 flex items-center gap-2"> |
|
<i class="fas fa-user-plus"></i> Inscription Joueur |
|
</h2> |
|
<form id="playerForm" class="space-y-4"> |
|
<input type="hidden" id="playerId"> |
|
<div> |
|
<label class="block mb-1 font-medium">Nom complet</label> |
|
<input type="text" id="playerName" class="w-full p-2 border rounded-lg bg-white dark:bg-gray-700" required> |
|
</div> |
|
<div> |
|
<label class="block mb-1 font-medium">Niveau (1-10)</label> |
|
<input type="number" id="playerLevel" min="1" max="10" class="w-full p-2 border rounded-lg bg-white dark:bg-gray-700" value="5"> |
|
</div> |
|
<div> |
|
<label class="block mb-1 font-medium">Équipe favorite</label> |
|
<select id="playerTeam" class="w-full p-2 border rounded-lg bg-white dark:bg-gray-700"> |
|
<option value="">Aucune</option> |
|
<option value="PSG">PSG</option> |
|
<option value="Real Madrid">Real Madrid</option> |
|
<option value="FC Barcelone">FC Barcelone</option> |
|
<option value="Manchester City">Manchester City</option> |
|
<option value="Bayern Munich">Bayern Munich</option> |
|
<option value="Liverpool">Liverpool</option> |
|
<option value="Juventus">Juventus</option> |
|
<option value="Inter Milan">Inter Milan</option> |
|
</select> |
|
</div> |
|
<div> |
|
<label class="block mb-1 font-medium">Participation (€)</label> |
|
<input type="number" id="playerParticipation" min="0" class="w-full p-2 border rounded-lg bg-white dark:bg-gray-700" value="0"> |
|
</div> |
|
<button type="submit" class="bg-blue-500 hover:bg-blue-600 text-white w-full py-2 rounded-lg flex items-center justify-center gap-2 transition"> |
|
<i class="fas fa-save"></i> Enregistrer |
|
</button> |
|
</form> |
|
</div> |
|
|
|
|
|
<div class="bg-white dark:bg-gray-800 p-6 rounded-xl shadow md:col-span-2"> |
|
<div class="flex justify-between items-center mb-4"> |
|
<h2 class="text-xl font-bold flex items-center gap-2"> |
|
<i class="fas fa-list"></i> Liste des Joueurs |
|
</h2> |
|
<div class="relative w-64"> |
|
<input type="text" id="playerSearch" placeholder="Rechercher un joueur..." class="w-full p-2 pl-8 border rounded-lg bg-white dark:bg-gray-700"> |
|
<i class="fas fa-search absolute left-2 top-3 text-gray-400"></i> |
|
</div> |
|
</div> |
|
|
|
<div class="overflow-x-auto"> |
|
<table class="min-w-full"> |
|
<thead> |
|
<tr class="border-b border-gray-200 dark:border-gray-700"> |
|
<th class="text-left py-3 px-4">Nom</th> |
|
<th class="text-left py-3 px-4">Niveau</th> |
|
<th class="text-left py-3 px-4">Équipe</th> |
|
<th class="text-left py-3 px-4">Participation</th> |
|
<th class="text-left py-3 px-4">Actions</th> |
|
</tr> |
|
</thead> |
|
<tbody id="playersList"> |
|
|
|
</tbody> |
|
</table> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="schedule" class="tab-content"> |
|
<div class="bg-white dark:bg-gray-800 p-6 rounded-xl shadow"> |
|
<div class="flex justify-between items-center mb-4"> |
|
<h2 class="text-xl font-bold flex items-center gap-2"> |
|
<i class="fas fa-calendar"></i> Calendrier des Matchs |
|
</h2> |
|
<button id="createNewMatchBtn" class="bg-green-500 hover:bg-green-600 text-white px-4 py-2 rounded-lg flex items-center gap-2 transition"> |
|
<i class="fas fa-plus"></i> Nouveau Match |
|
</button> |
|
</div> |
|
|
|
<div class="mb-4 flex items-center gap-4"> |
|
<div class="flex-1"> |
|
<label class="block mb-1 font-medium">Filtrer par date</label> |
|
<input type="date" id="dateFilter" class="w-full p-2 border rounded-lg bg-white dark:bg-gray-700"> |
|
</div> |
|
<div class="flex-1"> |
|
<label class="block mb-1 font-medium">Filtrer par statut</label> |
|
<select id="statusFilter" class="w-full p-2 border rounded-lg bg-white dark:bg-gray-700"> |
|
<option value="all">Tous</option> |
|
<option value="upcoming">À venir</option> |
|
<option value="completed">Terminés</option> |
|
</select> |
|
</div> |
|
</div> |
|
|
|
<div class="space-y-4" id="matchesList"> |
|
|
|
<div class="text-center py-8 text-gray-500 dark:text-gray-400"> |
|
<i class="fas fa-calendar-plus text-4xl mb-2"></i> |
|
<p>Aucun match programmé</p> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="results" class="tab-content"> |
|
<div class="bg-white dark:bg-gray-800 p-6 rounded-xl shadow"> |
|
<h2 class="text-xl font-bold mb-4 flex items-center gap-2"> |
|
<i class="fas fa-trophy"></i> Classement des Joueurs |
|
</h2> |
|
|
|
<div class="overflow-x-auto"> |
|
<table class="min-w-full"> |
|
<thead> |
|
<tr class="border-b border-gray-200 dark:border-gray-700"> |
|
<th class="text-left py-3 px-4">#</th> |
|
<th class="text-left py-3 px-4">Joueur</th> |
|
<th class="text-left py-3 px-4">Matchs</th> |
|
<th class="text-left py-3 px-4">Victoires</th> |
|
<th class="text-left py-3 px-4">Nuls</th> |
|
<th class="text-left py-3 px-4">Défaites</th> |
|
<th class="text-left py-3 px-4">Points</th> |
|
</tr> |
|
</thead> |
|
<tbody id="rankingList"> |
|
|
|
<tr> |
|
<td colspan="7" class="text-center py-8 text-gray-500 dark:text-gray-400"> |
|
<i class="fas fa-info-circle text-4xl mb-2"></i> |
|
<p>Aucun résultat disponible</p> |
|
</td> |
|
</tr> |
|
</tbody> |
|
</table> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="pronostics" class="tab-content"> |
|
<div class="bg-white dark:bg-gray-800 p-6 rounded-xl shadow"> |
|
<h2 class="text-xl font-bold mb-4 flex items-center gap-2"> |
|
<i class="fas fa-binoculars"></i> Mes Pronostics |
|
</h2> |
|
|
|
<div class="space-y-4" id="pronosticsList"> |
|
|
|
<div class="text-center py-8 text-gray-500 dark:text-gray-400"> |
|
<i class="fas fa-info-circle text-4xl mb-2"></i> |
|
<p>Aucun pronostic enregistré</p> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="settings" class="tab-content"> |
|
<div class="bg-white dark:bg-gray-800 p-6 rounded-xl shadow"> |
|
<h2 class="text-xl font-bold mb-4 flex items-center gap-2"> |
|
<i class="fas fa-cog"></i> Paramètres du Tournoi |
|
</h2> |
|
|
|
<form id="settingsForm" class="space-y-4"> |
|
<div> |
|
<label class="block mb-1 font-medium">Nom du tournoi</label> |
|
<input type="text" id="tournamentName" class="w-full p-2 border rounded-lg bg-white dark:bg-gray-700" value="FIFA Tournament Pro"> |
|
</div> |
|
|
|
<div> |
|
<label class="block mb-1 font-medium">Type de tournoi</label> |
|
<select id="tournamentType" class="w-full p-2 border rounded-lg bg-white dark:bg-gray-700"> |
|
<option value="league">Championnat</option> |
|
<option value="knockout">Élimination directe</option> |
|
<option value="groups">Groupes + Phase finale</option> |
|
</select> |
|
</div> |
|
|
|
<div> |
|
<label class="block mb-1 font-medium">Points pour une victoire</label> |
|
<input type="number" id="winPoints" min="1" class="w-full p-2 border rounded-lg bg-white dark:bg-gray-700" value="3"> |
|
</div> |
|
|
|
<div> |
|
<label class="block mb-1 font-medium">Points pour un nul</label> |
|
<input type="number" id="drawPoints" min="0" class="w-full p-2 border rounded-lg bg-white dark:bg-gray-700" value="1"> |
|
</div> |
|
|
|
<div> |
|
<label class="block mb-1 font-medium">Points pour une défaite</label> |
|
<input type="number" id="lossPoints" min="0" class="w-full p-2 border rounded-lg bg-white dark:bg-gray-700" value="0"> |
|
</div> |
|
|
|
<button type="submit" class="bg-blue-500 hover:bg-blue-600 text-white w-full py-2 rounded-lg flex items-center justify-center gap-2 transition"> |
|
<i class="fas fa-save"></i> Enregistrer les paramètres |
|
</button> |
|
</form> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="matchModal" class="modal"> |
|
<div class="modal-content"> |
|
<div class="flex justify-between items-center mb-4"> |
|
<h3 class="text-xl font-bold" id="matchModalTitle">Nouveau Match</h3> |
|
<button id="closeMatchModal" class="text-gray-500 hover:text-gray-700 dark:hover:text-gray-300"> |
|
<i class="fas fa-times"></i> |
|
</button> |
|
</div> |
|
|
|
<form id="matchForm" class="space-y-4"> |
|
<input type="hidden" id="matchId"> |
|
|
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> |
|
<div> |
|
<label class="block mb-1 font-medium">Joueur 1</label> |
|
<select id="player1" class="w-full p-2 border rounded-lg bg-white dark:bg-gray-700" required> |
|
<option value="">Sélectionner un joueur</option> |
|
</select> |
|
</div> |
|
<div> |
|
<label class="block mb-1 font-medium">Joueur 2</label> |
|
<select id="player2" class="w-full p-2 border rounded-lg bg-white dark:bg-gray-700" required> |
|
<option value="">Sélectionner un joueur</option> |
|
</select> |
|
</div> |
|
</div> |
|
|
|
<div> |
|
<label class="block mb-1 font-medium">Date et heure</label> |
|
<input type="datetime-local" id="matchDateTime" class="w-full p-2 border rounded-lg bg-white dark:bg-gray-700" required> |
|
</div> |
|
|
|
<div> |
|
<label class="block mb-1 font-medium">Phase</label> |
|
<select id="matchPhase" class="w-full p-2 border rounded-lg bg-white dark:bg-gray-700"> |
|
<option value="group">Groupe</option> |
|
<option value="round16">8ème de finale</option> |
|
<option value="quarter">Quart de finale</option> |
|
<option value="semi">Demi-finale</option> |
|
<option value="final">Finale</option> |
|
</select> |
|
</div> |
|
|
|
<div id="scoreFields" class="hidden"> |
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4"> |
|
<div> |
|
<label class="block mb-1 font-medium">Score Joueur 1</label> |
|
<input type="number" id="score1" min="0" class="w-full p-2 border rounded-lg bg-white dark:bg-gray-700" value="0"> |
|
</div> |
|
<div> |
|
<label class="block mb-1 font-medium">Score Joueur 2</label> |
|
<input type="number" id="score2" min="0" class="w-full p-2 border rounded-lg bg-white dark:bg-gray-700" value="0"> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<div class="flex justify-between pt-4"> |
|
<button type="button" id="toggleScoreFields" class="bg-gray-200 hover:bg-gray-300 dark:bg-gray-700 dark:hover:bg-gray-600 px-4 py-2 rounded-lg transition"> |
|
<i class="fas fa-futbol mr-2"></i> Ajouter score |
|
</button> |
|
<button type="submit" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg flex items-center gap-2 transition"> |
|
<i class="fas fa-save"></i> Enregistrer |
|
</button> |
|
</div> |
|
</form> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="toast" class="toast bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg"> |
|
<div class="flex items-center"> |
|
<i class="fas fa-check-circle mr-2"></i> |
|
<span id="toastMessage">Opération réussie</span> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
<script> |
|
|
|
let players = []; |
|
let matches = []; |
|
let tournamentSettings = { |
|
name: "FIFA Tournament Pro", |
|
type: "league", |
|
winPoints: 3, |
|
drawPoints: 1, |
|
lossPoints: 0 |
|
}; |
|
|
|
|
|
const tabButtons = document.querySelectorAll('.tab-btn'); |
|
const tabContents = document.querySelectorAll('.tab-content'); |
|
const themeToggle = document.getElementById('themeToggle'); |
|
const currentDateElement = document.getElementById('currentDate'); |
|
const playerForm = document.getElementById('playerForm'); |
|
const playerIdInput = document.getElementById('playerId'); |
|
const playerNameInput = document.getElementById('playerName'); |
|
const playerLevelInput = document.getElementById('playerLevel'); |
|
const playerTeamInput = document.getElementById('playerTeam'); |
|
const playerParticipationInput = document.getElementById('playerParticipation'); |
|
const playersList = document.getElementById('playersList'); |
|
const playerSearch = document.getElementById('playerSearch'); |
|
const playerCountElement = document.getElementById('playerCount'); |
|
const matchCountElement = document.getElementById('matchCount'); |
|
const completedCountElement = document.getElementById('completedCount'); |
|
const upcomingCountElement = document.getElementById('upcomingCount'); |
|
const nextMatchCard = document.getElementById('nextMatchCard'); |
|
const winnerCard = document.getElementById('winnerCard'); |
|
const recentResults = document.getElementById('recentResults'); |
|
const createMatchBtn = document.getElementById('createMatchBtn'); |
|
const generateTournamentBtn = document.getElementById('generateTournamentBtn'); |
|
const matchModal = document.getElementById('matchModal'); |
|
const closeMatchModal = document.getElementById('closeMatchModal'); |
|
const matchForm = document.getElementById('matchForm'); |
|
const matchIdInput = document.getElementById('matchId'); |
|
const player1Select = document.getElementById('player1'); |
|
const player2Select = document.getElementById('player2'); |
|
const matchDateTimeInput = document.getElementById('matchDateTime'); |
|
const matchPhaseSelect = document.getElementById('matchPhase'); |
|
const scoreFields = document.getElementById('scoreFields'); |
|
const score1Input = document.getElementById('score1'); |
|
const score2Input = document.getElementById('score2'); |
|
const toggleScoreFields = document.getElementById('toggleScoreFields'); |
|
const matchesList = document.getElementById('matchesList'); |
|
const dateFilter = document.getElementById('dateFilter'); |
|
const statusFilter = document.getElementById('statusFilter'); |
|
const rankingList = document.getElementById('rankingList'); |
|
const pronosticsList = document.getElementById('pronosticsList'); |
|
const settingsForm = document.getElementById('settingsForm'); |
|
const tournamentNameInput = document.getElementById('tournamentName'); |
|
const tournamentTypeSelect = document.getElementById('tournamentType'); |
|
const winPointsInput = document.getElementById('winPoints'); |
|
const drawPointsInput = document.getElementById('drawPoints'); |
|
const lossPointsInput = document.getElementById('lossPoints'); |
|
const toast = document.getElementById('toast'); |
|
const toastMessage = document.getElementById('toastMessage'); |
|
const tournamentTitle = document.getElementById('tournamentTitle'); |
|
const createNewMatchBtn = document.getElementById('createNewMatchBtn'); |
|
|
|
|
|
function init() { |
|
|
|
const now = new Date(); |
|
currentDateElement.textContent = now.toLocaleDateString('fr-FR', { |
|
weekday: 'long', |
|
year: 'numeric', |
|
month: 'long', |
|
day: 'numeric' |
|
}); |
|
|
|
|
|
loadData(); |
|
|
|
|
|
setupEventListeners(); |
|
|
|
|
|
renderPlayers(); |
|
renderMatches(); |
|
updateStats(); |
|
updateNextMatch(); |
|
updateRecentResults(); |
|
updateRanking(); |
|
updateTournamentSettingsUI(); |
|
} |
|
|
|
|
|
function loadData() { |
|
const savedPlayers = localStorage.getItem('fifaPlayers'); |
|
const savedMatches = localStorage.getItem('fifaMatches'); |
|
const savedSettings = localStorage.getItem('fifaSettings'); |
|
|
|
if (savedPlayers) players = JSON.parse(savedPlayers); |
|
if (savedMatches) matches = JSON.parse(savedMatches); |
|
if (savedSettings) tournamentSettings = JSON.parse(savedSettings); |
|
|
|
|
|
tournamentTitle.textContent = tournamentSettings.name; |
|
tournamentTitle.classList.add('goat-title'); |
|
} |
|
|
|
|
|
function saveData() { |
|
localStorage.setItem('fifaPlayers', JSON.stringify(players)); |
|
localStorage.setItem('fifaMatches', JSON.stringify(matches)); |
|
localStorage.setItem('fifaSettings', JSON.stringify(tournamentSettings)); |
|
} |
|
|
|
|
|
function setupEventListeners() { |
|
|
|
tabButtons.forEach(button => { |
|
button.addEventListener('click', () => { |
|
const tabId = button.getAttribute('data-tab'); |
|
switchTab(tabId); |
|
}); |
|
}); |
|
|
|
|
|
document.querySelectorAll('[data-tab-target]').forEach(button => { |
|
button.addEventListener('click', () => { |
|
const tabId = button.getAttribute('data-tab-target'); |
|
switchTab(tabId); |
|
}); |
|
}); |
|
|
|
|
|
themeToggle.addEventListener('click', toggleTheme); |
|
|
|
|
|
playerForm.addEventListener('submit', handlePlayerFormSubmit); |
|
|
|
|
|
playerSearch.addEventListener('input', () => { |
|
renderPlayers(playerSearch.value.toLowerCase()); |
|
}); |
|
|
|
|
|
createMatchBtn.addEventListener('click', openMatchModal); |
|
createNewMatchBtn.addEventListener('click', openMatchModal); |
|
|
|
|
|
closeMatchModal.addEventListener('click', closeMatchModalFunc); |
|
matchForm.addEventListener('submit', handleMatchFormSubmit); |
|
toggleScoreFields.addEventListener('click', toggleScoreFieldsFunc); |
|
|
|
|
|
dateFilter.addEventListener('change', () => { |
|
renderMatches(); |
|
}); |
|
|
|
|
|
statusFilter.addEventListener('change', () => { |
|
renderMatches(); |
|
}); |
|
|
|
|
|
settingsForm.addEventListener('submit', handleSettingsFormSubmit); |
|
|
|
|
|
generateTournamentBtn.addEventListener('click', generateRandomTournament); |
|
} |
|
|
|
|
|
function switchTab(tabId) { |
|
|
|
tabButtons.forEach(button => { |
|
button.classList.remove('active', 'bg-blue-500', 'text-white'); |
|
button.classList.add('hover:bg-gray-200', 'dark:hover:bg-gray-700'); |
|
|
|
if (button.getAttribute('data-tab') === tabId) { |
|
button.classList.add('active', 'bg-blue-500', 'text-white'); |
|
button.classList.remove('hover:bg-gray-200', 'dark:hover:bg-gray-700'); |
|
} |
|
}); |
|
|
|
|
|
tabContents.forEach(content => { |
|
content.classList.remove('active'); |
|
|
|
if (content.id === tabId) { |
|
content.classList.add('active'); |
|
|
|
|
|
if (tabId === 'players') { |
|
renderPlayers(); |
|
} else if (tabId === 'schedule') { |
|
renderMatches(); |
|
} else if (tabId === 'results') { |
|
updateRanking(); |
|
} |
|
} |
|
}); |
|
} |
|
|
|
|
|
function toggleTheme() { |
|
document.documentElement.classList.toggle('dark'); |
|
localStorage.setItem('darkMode', document.documentElement.classList.contains('dark')); |
|
} |
|
|
|
|
|
if (localStorage.getItem('darkMode') === 'true') { |
|
document.documentElement.classList.add('dark'); |
|
} |
|
|
|
|
|
function handlePlayerFormSubmit(e) { |
|
e.preventDefault(); |
|
|
|
const playerData = { |
|
id: playerIdInput.value || Date.now().toString(), |
|
name: playerNameInput.value, |
|
level: parseInt(playerLevelInput.value), |
|
team: playerTeamInput.value, |
|
participation: parseFloat(playerParticipationInput.value) || 0, |
|
matchesPlayed: 0, |
|
wins: 0, |
|
draws: 0, |
|
losses: 0, |
|
points: 0 |
|
}; |
|
|
|
if (playerIdInput.value) { |
|
|
|
const index = players.findIndex(p => p.id === playerIdInput.value); |
|
if (index !== -1) { |
|
|
|
playerData.matchesPlayed = players[index].matchesPlayed; |
|
playerData.wins = players[index].wins; |
|
playerData.draws = players[index].draws; |
|
playerData.losses = players[index].losses; |
|
playerData.points = players[index].points; |
|
|
|
players[index] = playerData; |
|
showToast('Joueur mis à jour avec succès'); |
|
} |
|
} else { |
|
|
|
players.push(playerData); |
|
showToast('Joueur ajouté avec succès'); |
|
} |
|
|
|
|
|
playerForm.reset(); |
|
playerIdInput.value = ''; |
|
|
|
|
|
renderPlayers(); |
|
updateStats(); |
|
saveData(); |
|
} |
|
|
|
|
|
function renderPlayers(searchTerm = '') { |
|
|
|
playersList.innerHTML = ''; |
|
|
|
|
|
const filteredPlayers = searchTerm |
|
? players.filter(player => |
|
player.name.toLowerCase().includes(searchTerm) || |
|
(player.team && player.team.toLowerCase().includes(searchTerm))) |
|
: players; |
|
|
|
if (filteredPlayers.length === 0) { |
|
playersList.innerHTML = ` |
|
<tr> |
|
<td colspan="5" class="text-center py-8 text-gray-500 dark:text-gray-400"> |
|
<i class="fas fa-user-slash text-4xl mb-2"></i> |
|
<p>Aucun joueur trouvé</p> |
|
</td> |
|
</tr> |
|
`; |
|
return; |
|
} |
|
|
|
|
|
filteredPlayers.forEach(player => { |
|
const row = document.createElement('tr'); |
|
row.className = 'border-b border-gray-200 dark:border-gray-700 hover:bg-gray-100 dark:hover:bg-gray-700'; |
|
row.innerHTML = ` |
|
<td class="py-3 px-4">${player.name}</td> |
|
<td class="py-3 px-4"> |
|
<div class="flex items-center"> |
|
${renderLevelStars(player.level)} |
|
</div> |
|
</td> |
|
<td class="py-3 px-4">${player.team || '-'}</td> |
|
<td class="py-3 px-4">${player.participation.toFixed(2)} €</td> |
|
<td class="py-3 px-4"> |
|
<button class="edit-player-btn mr-2 text-blue-500 hover:text-blue-700 dark:hover:text-blue-400" data-id="${player.id}"> |
|
<i class="fas fa-edit"></i> |
|
</button> |
|
<button class="delete-player-btn text-red-500 hover:text-red-700 dark:hover:text-red-400" data-id="${player.id}"> |
|
<i class="fas fa-trash"></i> |
|
</button> |
|
</td> |
|
`; |
|
playersList.appendChild(row); |
|
}); |
|
|
|
|
|
document.querySelectorAll('.edit-player-btn').forEach(button => { |
|
button.addEventListener('click', () => editPlayer(button.getAttribute('data-id'))); |
|
}); |
|
|
|
document.querySelectorAll('.delete-player-btn').forEach(button => { |
|
button.addEventListener('click', () => deletePlayer(button.getAttribute('data-id'))); |
|
}); |
|
|
|
|
|
updatePlayerSelects(); |
|
} |
|
|
|
|
|
function renderLevelStars(level) { |
|
let stars = ''; |
|
for (let i = 1; i <= 10; i++) { |
|
stars += i <= level |
|
? '<i class="fas fa-star text-yellow-500"></i>' |
|
: '<i class="far fa-star text-yellow-500"></i>'; |
|
} |
|
return stars; |
|
} |
|
|
|
|
|
function editPlayer(playerId) { |
|
const player = players.find(p => p.id === playerId); |
|
if (player) { |
|
playerIdInput.value = player.id; |
|
playerNameInput.value = player.name; |
|
playerLevelInput.value = player.level; |
|
playerTeamInput.value = player.team || ''; |
|
playerParticipationInput.value = player.participation; |
|
|
|
|
|
document.getElementById('playerForm').scrollIntoView({ behavior: 'smooth' }); |
|
} |
|
} |
|
|
|
|
|
function deletePlayer(playerId) { |
|
if (confirm('Êtes-vous sûr de vouloir supprimer ce joueur ?')) { |
|
players = players.filter(p => p.id !== playerId); |
|
|
|
|
|
matches = matches.filter(m => m.player1Id !== playerId && m.player2Id !== playerId); |
|
|
|
showToast('Joueur supprimé avec succès'); |
|
renderPlayers(); |
|
renderMatches(); |
|
updateStats(); |
|
updateNextMatch(); |
|
updateRecentResults(); |
|
updateRanking(); |
|
saveData(); |
|
} |
|
} |
|
|
|
|
|
function updatePlayerSelects() { |
|
|
|
player1Select.innerHTML = '<option value="">Sélectionner un joueur</option>'; |
|
player2Select.innerHTML = '<option value="">Sélectionner un joueur</option>'; |
|
|
|
|
|
players.forEach(player => { |
|
const option1 = document.createElement('option'); |
|
option1.value = player.id; |
|
option1.textContent = player.name; |
|
|
|
const option2 = document.createElement('option'); |
|
option2.value = player.id; |
|
option2.textContent = player.name; |
|
|
|
player1Select.appendChild(option1); |
|
player2Select.appendChild(option2); |
|
}); |
|
} |
|
|
|
|
|
function openMatchModal(matchId = null) { |
|
|
|
matchForm.reset(); |
|
matchIdInput.value = ''; |
|
scoreFields.classList.add('hidden'); |
|
|
|
|
|
if (matchId) { |
|
const match = matches.find(m => m.id === matchId); |
|
if (match) { |
|
matchIdInput.value = match.id; |
|
player1Select.value = match.player1Id; |
|
player2Select.value = match.player2Id; |
|
matchDateTimeInput.value = match.dateTime; |
|
matchPhaseSelect.value = match.phase || 'group'; |
|
|
|
if (match.score1 !== undefined && match.score2 !== undefined) { |
|
score1Input.value = match.score1; |
|
score2Input.value = match.score2; |
|
scoreFields.classList.remove('hidden'); |
|
toggleScoreFields.innerHTML = '<i class="fas fa-edit mr-2"></i> Modifier score'; |
|
} |
|
|
|
document.getElementById('matchModalTitle').textContent = 'Modifier Match'; |
|
} |
|
} else { |
|
document.getElementById('matchModalTitle').textContent = 'Nouveau Match'; |
|
} |
|
|
|
|
|
matchModal.classList.add('active'); |
|
} |
|
|
|
|
|
function closeMatchModalFunc() { |
|
matchModal.classList.remove('active'); |
|
} |
|
|
|
|
|
function toggleScoreFieldsFunc() { |
|
scoreFields.classList.toggle('hidden'); |
|
if (scoreFields.classList.contains('hidden')) { |
|
toggleScoreFields.innerHTML = '<i class="fas fa-futbol mr-2"></i> Ajouter score'; |
|
} else { |
|
toggleScoreFields.innerHTML = '<i class="fas fa-edit mr-2"></i> Modifier score'; |
|
} |
|
} |
|
|
|
|
|
function handleMatchFormSubmit(e) { |
|
e.preventDefault(); |
|
|
|
|
|
const player1 = players.find(p => p.id === player1Select.value); |
|
const player2 = players.find(p => p.id === player2Select.value); |
|
|
|
if (!player1 || !player2) { |
|
showToast('Veuillez sélectionner deux joueurs', 'error'); |
|
return; |
|
} |
|
|
|
if (player1.id === player2.id) { |
|
showToast('Un joueur ne peut pas jouer contre lui-même', 'error'); |
|
return; |
|
} |
|
|
|
const matchData = { |
|
id: matchIdInput.value || Date.now().toString(), |
|
player1Id: player1.id, |
|
player1Name: player1.name, |
|
player2Id: player2.id, |
|
player2Name: player2.name, |
|
dateTime: matchDateTimeInput.value, |
|
phase: matchPhaseSelect.value, |
|
completed: scoreFields.classList.contains('hidden') ? false : true, |
|
score1: scoreFields.classList.contains('hidden') ? undefined : parseInt(score1Input.value), |
|
score2: scoreFields.classList.contains('hidden') ? undefined : parseInt(score2Input.value) |
|
}; |
|
|
|
if (matchIdInput.value) { |
|
|
|
const index = matches.findIndex(m => m.id === matchIdInput.value); |
|
if (index !== -1) { |
|
matches[index] = matchData; |
|
showToast('Match mis à jour avec succès'); |
|
} |
|
} else { |
|
|
|
matches.push(matchData); |
|
showToast('Match ajouté avec succès'); |
|
} |
|
|
|
|
|
closeMatchModalFunc(); |
|
renderMatches(); |
|
updateStats(); |
|
updateNextMatch(); |
|
updateRecentResults(); |
|
updateRanking(); |
|
saveData(); |
|
} |
|
|
|
|
|
function renderMatches() { |
|
|
|
matchesList.innerHTML = ''; |
|
|
|
|
|
let filteredMatches = [...matches]; |
|
|
|
|
|
if (dateFilter.value) { |
|
const selectedDate = new Date(dateFilter.value).toDateString(); |
|
filteredMatches = filteredMatches.filter(match => { |
|
const matchDate = new Date(match.dateTime).toDateString(); |
|
return matchDate === selectedDate; |
|
}); |
|
} |
|
|
|
|
|
if (statusFilter.value !== 'all') { |
|
filteredMatches = filteredMatches.filter(match => { |
|
if (statusFilter.value === 'upcoming') return !match.completed; |
|
if (statusFilter.value === 'completed') return match.completed; |
|
}); |
|
} |
|
|
|
|
|
filteredMatches.sort((a, b) => new Date(a.dateTime) - new Date(b.dateTime)); |
|
|
|
if (filteredMatches.length === 0) { |
|
matchesList.innerHTML = ` |
|
<div class="text-center py-8 text-gray-500 dark:text-gray-400"> |
|
<i class="fas fa-calendar-plus text-4xl mb-2"></i> |
|
<p>Aucun match trouvé</p> |
|
</div> |
|
`; |
|
return; |
|
} |
|
|
|
|
|
if (tournamentSettings.type === 'knockout' || tournamentSettings.type === 'groups') { |
|
const phases = { |
|
'group': 'Phase de groupes', |
|
'round16': '8ème de finale', |
|
'quarter': 'Quart de finale', |
|
'semi': 'Demi-finale', |
|
'final': 'Finale' |
|
}; |
|
|
|
const groupedMatches = {}; |
|
|
|
filteredMatches.forEach(match => { |
|
const phase = match.phase || 'group'; |
|
if (!groupedMatches[phase]) { |
|
groupedMatches[phase] = []; |
|
} |
|
groupedMatches[phase].push(match); |
|
}); |
|
|
|
|
|
Object.keys(groupedMatches).forEach(phase => { |
|
const phaseMatches = groupedMatches[phase]; |
|
|
|
const phaseHeader = document.createElement('div'); |
|
phaseHeader.className = 'mb-4'; |
|
phaseHeader.innerHTML = ` |
|
<h3 class="text-lg font-bold mb-2 ${phase !== 'group' ? 'text-yellow-600 dark:text-yellow-400' : ''}"> |
|
${phases[phase]} |
|
</h3> |
|
`; |
|
matchesList.appendChild(phaseHeader); |
|
|
|
phaseMatches.forEach(match => { |
|
matchesList.appendChild(createMatchCard(match)); |
|
}); |
|
}); |
|
} else { |
|
|
|
filteredMatches.forEach(match => { |
|
matchesList.appendChild(createMatchCard(match)); |
|
}); |
|
} |
|
} |
|
|
|
|
|
function createMatchCard(match) { |
|
const matchDate = new Date(match.dateTime); |
|
const now = new Date(); |
|
const isPast = matchDate < now; |
|
|
|
const card = document.createElement('div'); |
|
card.className = `bg-gray-100 dark:bg-gray-700 p-4 rounded-lg match-card ${match.phase && match.phase !== 'group' ? 'phase-finale' : ''}`; |
|
|
|
|
|
const header = document.createElement('div'); |
|
header.className = 'flex justify-between items-center mb-2'; |
|
header.innerHTML = ` |
|
<div class="text-sm font-medium ${isPast ? 'text-gray-500 dark:text-gray-400' : 'text-blue-600 dark:text-blue-400'}"> |
|
${matchDate.toLocaleString('fr-FR', { |
|
weekday: 'short', |
|
day: 'numeric', |
|
month: 'short', |
|
hour: '2-digit', |
|
minute: '2-digit' |
|
})} |
|
</div> |
|
<div> |
|
<button class="edit-match-btn mr-2 text-blue-500 hover:text-blue-700 dark:hover:text-blue-400" data-id="${match.id}"> |
|
<i class="fas fa-edit"></i> |
|
</button> |
|
<button class="delete-match-btn text-red-500 hover:text-red-700 dark:hover:text-red-400" data-id="${match.id}"> |
|
<i class="fas fa-trash"></i> |
|
</button> |
|
</div> |
|
`; |
|
|
|
|
|
const content = document.createElement('div'); |
|
content.className = 'flex justify-between items-center'; |
|
|
|
if (match.completed) { |
|
|
|
const player1 = players.find(p => p.id === match.player1Id); |
|
const player2 = players.find(p => p.id === match.player2Id); |
|
|
|
const player1Level = player1 ? player1.level : 5; |
|
const player2Level = player2 ? player2.level : 5; |
|
|
|
content.innerHTML = ` |
|
<div class="flex-1 text-right pr-4"> |
|
<div class="font-bold">${match.player1Name}</div> |
|
<div class="text-sm">${renderLevelStars(player1Level)}</div> |
|
</div> |
|
<div class="text-center px-4"> |
|
<div class="text-2xl font-bold"> |
|
${match.score1} - ${match.score2} |
|
</div> |
|
<div class="text-xs ${match.score1 === match.score2 ? 'text-gray-500' : match.score1 > match.score2 ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'}"> |
|
${match.score1 === match.score2 ? 'Match nul' : match.score1 > match.score2 ? 'Victoire' : 'Défaite'} |
|
</div> |
|
</div> |
|
<div class="flex-1 text-left pl-4"> |
|
<div class="font-bold">${match.player2Name}</div> |
|
<div class="text-sm">${renderLevelStars(player2Level)}</div> |
|
</div> |
|
`; |
|
} else { |
|
|
|
content.innerHTML = ` |
|
<div class="flex-1 text-right pr-4"> |
|
<div class="font-bold">${match.player1Name}</div> |
|
</div> |
|
<div class="text-center px-4"> |
|
<div class="text-xl">VS</div> |
|
</div> |
|
<div class="flex-1 text-left pl-4"> |
|
<div class="font-bold">${match.player2Name}</div> |
|
</div> |
|
`; |
|
} |
|
|
|
card.appendChild(header); |
|
card.appendChild(content); |
|
|
|
|
|
card.querySelector('.edit-match-btn').addEventListener('click', () => { |
|
openMatchModal(card.querySelector('.edit-match-btn').getAttribute('data-id')); |
|
}); |
|
|
|
card.querySelector('.delete-match-btn').addEventListener('click', () => { |
|
deleteMatch(card.querySelector('.delete-match-btn').getAttribute('data-id')); |
|
}); |
|
|
|
return card; |
|
} |
|
|
|
|
|
function deleteMatch(matchId) { |
|
if (confirm('Êtes-vous sûr de vouloir supprimer ce match ?')) { |
|
matches = matches.filter(m => m.id !== matchId); |
|
showToast('Match supprimé avec succès'); |
|
renderMatches(); |
|
updateStats(); |
|
updateNextMatch(); |
|
updateRecentResults(); |
|
updateRanking(); |
|
saveData(); |
|
} |
|
} |
|
|
|
|
|
function updateStats() { |
|
playerCountElement.textContent = players.length; |
|
matchCountElement.textContent = matches.length; |
|
|
|
const completedMatches = matches.filter(m => m.completed).length; |
|
const upcomingMatches = matches.filter(m => !m.completed).length; |
|
|
|
completedCountElement.textContent = completedMatches; |
|
upcomingCountElement.textContent = upcomingMatches; |
|
} |
|
|
|
|
|
function updateNextMatch() { |
|
const now = new Date(); |
|
const upcomingMatches = matches |
|
.filter(m => !m.completed && new Date(m.dateTime) > now) |
|
.sort((a, b) => new Date(a.dateTime) - new Date(b.dateTime)); |
|
|
|
if (upcomingMatches.length > 0) { |
|
const nextMatch = upcomingMatches[0]; |
|
const matchDate = new Date(nextMatch.dateTime); |
|
|
|
nextMatchCard.innerHTML = ` |
|
<div class="flex justify-between items-center mb-2"> |
|
<div class="text-sm font-medium text-blue-600 dark:text-blue-400"> |
|
${matchDate.toLocaleString('fr-FR', { |
|
weekday: 'long', |
|
day: 'numeric', |
|
month: 'long', |
|
hour: '2-digit', |
|
minute: '2-digit' |
|
})} |
|
</div> |
|
<div class="text-xs bg-blue-100 dark:bg-blue-900 text-blue-800 dark:text-blue-200 px-2 py-1 rounded"> |
|
${nextMatch.phase === 'group' ? 'Groupe' : |
|
nextMatch.phase === 'round16' ? '8ème de finale' : |
|
nextMatch.phase === 'quarter' ? 'Quart de finale' : |
|
nextMatch.phase === 'semi' ? 'Demi-finale' : 'Finale'} |
|
</div> |
|
</div> |
|
<div class="flex justify-between items-center"> |
|
<div class="text-center flex-1"> |
|
<div class="font-bold">${nextMatch.player1Name}</div> |
|
<div class="text-sm">VS</div> |
|
<div class="font-bold">${nextMatch.player2Name}</div> |
|
</div> |
|
</div> |
|
`; |
|
} else { |
|
nextMatchCard.innerHTML = ` |
|
<div class="text-center py-8 text-gray-500 dark:text-gray-400"> |
|
<i class="fas fa-calendar-plus text-4xl mb-2"></i> |
|
<p>Aucun match programmé</p> |
|
</div> |
|
`; |
|
} |
|
} |
|
|
|
|
|
function updateRecentResults() { |
|
const now = new Date(); |
|
const completedMatches = matches |
|
.filter(m => m.completed) |
|
.sort((a, b) => new Date(b.dateTime) - new Date(a.dateTime)) |
|
.slice(0, 5); |
|
|
|
recentResults.innerHTML = ''; |
|
|
|
if (completedMatches.length === 0) { |
|
recentResults.innerHTML = ` |
|
<div class="text-center py-8 text-gray-500 dark:text-gray-400"> |
|
<i class="fas fa-info-circle text-4xl mb-2"></i> |
|
<p>Aucun résultat récent</p> |
|
</div> |
|
`; |
|
return; |
|
} |
|
|
|
completedMatches.forEach(match => { |
|
const matchDate = new Date(match.dateTime); |
|
const player1 = players.find(p => p.id === match.player1Id); |
|
const player2 = players.find(p => p.id === match.player2Id); |
|
|
|
const player1Level = player1 ? player1.level : 5; |
|
const player2Level = player2 ? player2.level : 5; |
|
|
|
const resultItem = document.createElement('div'); |
|
resultItem.className = 'bg-gray-100 dark:bg-gray-700 p-3 rounded-lg'; |
|
resultItem.innerHTML = ` |
|
<div class="flex justify-between items-center mb-1"> |
|
<div class="text-sm text-gray-500 dark:text-gray-400"> |
|
${matchDate.toLocaleString('fr-FR', { |
|
weekday: 'short', |
|
day: 'numeric', |
|
month: 'short', |
|
hour: '2-digit', |
|
minute: '2-digit' |
|
})} |
|
</div> |
|
<div class="text-xs bg-gray-200 dark:bg-gray-600 px-2 py-1 rounded"> |
|
${match.phase === 'group' ? 'Groupe' : |
|
match.phase === 'round16' ? '8ème de finale' : |
|
match.phase === 'quarter' ? 'Quart de finale' : |
|
match.phase === 'semi' ? 'Demi-finale' : 'Finale'} |
|
</div> |
|
</div> |
|
<div class="flex justify-between items-center"> |
|
<div class="flex-1 text-right pr-4"> |
|
<div>${match.player1Name}</div> |
|
<div class="text-xs">${renderLevelStars(player1Level)}</div> |
|
</div> |
|
<div class="text-center px-4"> |
|
<div class="text-xl font-bold ${match.score1 === match.score2 ? 'text-gray-500' : match.score1 > match.score2 ? 'text-green-600 dark:text-green-400' : 'text-red-600 dark:text-red-400'}"> |
|
${match.score1} - ${match.score2} |
|
</div> |
|
</div> |
|
<div class="flex-1 text-left pl-4"> |
|
<div>${match.player2Name}</div> |
|
<div class="text-xs">${renderLevelStars(player2Level)}</div> |
|
</div> |
|
</div> |
|
`; |
|
recentResults.appendChild(resultItem); |
|
}); |
|
} |
|
|
|
|
|
function updateRanking() { |
|
|
|
players.forEach(player => { |
|
player.matchesPlayed = 0; |
|
player.wins = 0; |
|
player.draws = 0; |
|
player.losses = 0; |
|
player.points = 0; |
|
}); |
|
|
|
|
|
matches.filter(m => m.completed).forEach(match => { |
|
const player1 = players.find(p => p.id === match.player1Id); |
|
const player2 = players.find(p => p.id === match.player2Id); |
|
|
|
if (player1 && player2) { |
|
player1.matchesPlayed++; |
|
player2.matchesPlayed++; |
|
|
|
if (match.score1 > match.score2) { |
|
player1.wins++; |
|
player2.losses++; |
|
player1.points += tournamentSettings.winPoints; |
|
player2.points += tournamentSettings.lossPoints; |
|
} else if (match.score1 < match.score2) { |
|
player1.losses++; |
|
player2.wins++; |
|
player1.points += tournamentSettings.lossPoints; |
|
player2.points += tournamentSettings.winPoints; |
|
} else { |
|
player1.draws++; |
|
player2.draws++; |
|
player1.points += tournamentSettings.drawPoints; |
|
player2.points += tournamentSettings.drawPoints; |
|
} |
|
} |
|
}); |
|
|
|
|
|
const sortedPlayers = [...players].sort((a, b) => b.points - a.points); |
|
|
|
|
|
rankingList.innerHTML = ''; |
|
|
|
if (sortedPlayers.length === 0) { |
|
rankingList.innerHTML = ` |
|
<tr> |
|
<td colspan="7" class="text-center py-8 text-gray-500 dark:text-gray-400"> |
|
<i class="fas fa-info-circle text-4xl mb-2"></i> |
|
<p>Aucun résultat disponible</p> |
|
</td> |
|
</tr> |
|
`; |
|
return; |
|
} |
|
|
|
sortedPlayers.forEach((player, index) => { |
|
const row = document.createElement('tr'); |
|
row.className = 'border-b border-gray-200 dark:border-gray-700 hover:bg-gray-100 dark:hover:bg-gray-700'; |
|
row.innerHTML = ` |
|
<td class="py-3 px-4">${index + 1}</td> |
|
<td class="py-3 px-4 font-bold">${player.name}</td> |
|
<td class="py-3 px-4">${player.matchesPlayed}</td> |
|
<td class="py-3 px-4 text-green-600 dark:text-green-400">${player.wins}</td> |
|
<td class="py-3 px-4 text-yellow-600 dark:text-yellow-400">${player.draws}</td> |
|
<td class="py-3 px-4 text-red-600 dark:text-red-400">${player.losses}</td> |
|
<td class="py-3 px-4 font-bold">${player.points}</td> |
|
`; |
|
rankingList.appendChild(row); |
|
}); |
|
|
|
|
|
const hasCompletedMatches = matches.some(m => m.completed); |
|
if (hasCompletedMatches && sortedPlayers.length > 0) { |
|
const winner = sortedPlayers[0]; |
|
winnerCard.innerHTML = ` |
|
<div class="flex flex-col items-center"> |
|
<div class="w-16 h-16 bg-yellow-500 rounded-full flex items-center justify-center text-white mb-2"> |
|
<i class="fas fa-trophy text-2xl"></i> |
|
</div> |
|
<h3 class="text-lg font-bold">${winner.name}</h3> |
|
<div class="text-sm">${winner.points} points</div> |
|
<div class="text-xs mt-2">${winner.wins}V ${winner.draws}N ${winner.losses}D</div> |
|
</div> |
|
`; |
|
winnerCard.classList.add('winner-card'); |
|
} else { |
|
winnerCard.innerHTML = ` |
|
<div class="py-8 text-gray-500 dark:text-gray-400"> |
|
<i class="fas fa-trophy text-4xl mb-2"></i> |
|
<p>Aucun vainqueur encore</p> |
|
</div> |
|
`; |
|
winnerCard.classList.remove('winner-card'); |
|
} |
|
} |
|
|
|
|
|
function handleSettingsFormSubmit(e) { |
|
e.preventDefault(); |
|
|
|
tournamentSettings = { |
|
name: tournamentNameInput.value, |
|
type: tournamentTypeSelect.value, |
|
winPoints: parseInt(winPointsInput.value), |
|
drawPoints: parseInt(drawPointsInput.value), |
|
lossPoints: parseInt(lossPointsInput.value) |
|
}; |
|
|
|
|
|
tournamentTitle.textContent = tournamentSettings.name; |
|
|
|
showToast('Paramètres enregistrés avec succès'); |
|
saveData(); |
|
updateRanking(); |
|
} |
|
|
|
|
|
function updateTournamentSettingsUI() { |
|
tournamentNameInput.value = tournamentSettings.name; |
|
tournamentTypeSelect.value = tournamentSettings.type; |
|
winPointsInput.value = tournamentSettings.winPoints; |
|
drawPointsInput.value = tournamentSettings.drawPoints; |
|
lossPointsInput.value = tournamentSettings.lossPoints; |
|
} |
|
|
|
|
|
function generateRandomTournament() { |
|
if (players.length < 4) { |
|
showToast('Vous avez besoin d\'au moins 4 joueurs pour générer un tournoi', 'error'); |
|
return; |
|
} |
|
|
|
if (confirm('Générer un tournoi aléatoire ? Cela effacera tous les matchs existants.')) { |
|
|
|
matches = []; |
|
|
|
|
|
if (tournamentSettings.type === 'groups') { |
|
|
|
const shuffledPlayers = [...players].sort(() => Math.random() - 0.5); |
|
const groupA = shuffledPlayers.slice(0, Math.ceil(players.length / 2)); |
|
const groupB = shuffledPlayers.slice(Math.ceil(players.length / 2)); |
|
|
|
|
|
createRoundRobinMatches(groupA, 'group'); |
|
createRoundRobinMatches(groupB, 'group'); |
|
|
|
|
|
const knockoutPhases = ['round16', 'quarter', 'semi', 'final']; |
|
let nextPhaseMatches = []; |
|
|
|
knockoutPhases.forEach(phase => { |
|
if (nextPhaseMatches.length === 0) { |
|
|
|
const groupAWinners = [...groupA].sort((a, b) => b.level - a.level).slice(0, 2); |
|
const groupBWinners = [...groupB].sort((a, b) => b.level - a.level).slice(0, 2); |
|
const allWinners = [...groupAWinners, ...groupBWinners].sort(() => Math.random() - 0.5); |
|
|
|
for (let i = 0; i < allWinners.length; i += 2) { |
|
if (i + 1 < allWinners.length) { |
|
nextPhaseMatches.push({ |
|
player1: allWinners[i], |
|
player2: allWinners[i + 1] |
|
}); |
|
} |
|
} |
|
} else { |
|
|
|
const newMatches = []; |
|
for (let i = 0; i < nextPhaseMatches.length; i += 2) { |
|
if (i + 1 < nextPhaseMatches.length) { |
|
newMatches.push({ |
|
player1: nextPhaseMatches[i].player1, |
|
player2: nextPhaseMatches[i + 1].player1 |
|
}); |
|
} |
|
} |
|
nextPhaseMatches = newMatches; |
|
} |
|
|
|
|
|
nextPhaseMatches.forEach((match, index) => { |
|
const matchDate = new Date(); |
|
matchDate.setDate(matchDate.getDate() + 7 * (knockoutPhases.indexOf(phase) + 1)); |
|
|
|
matches.push({ |
|
id: Date.now().toString() + index, |
|
player1Id: match.player1.id, |
|
player1Name: match.player1.name, |
|
player2Id: match.player2.id, |
|
player2Name: match.player2.name, |
|
dateTime: matchDate.toISOString(), |
|
phase: phase, |
|
completed: false |
|
}); |
|
}); |
|
}); |
|
} else if (tournamentSettings.type === 'knockout') { |
|
|
|
const shuffledPlayers = [...players].sort(() => Math.random() - 0.5); |
|
let currentRound = [...shuffledPlayers]; |
|
let phase = 'round16'; |
|
|
|
|
|
if (players.length <= 2) { |
|
phase = 'final'; |
|
} else if (players.length <= 4) { |
|
phase = 'semi'; |
|
} else if (players.length <= 8) { |
|
phase = 'quarter'; |
|
} |
|
|
|
while (currentRound.length > 1) { |
|
const nextRound = []; |
|
const matchesThisRound = []; |
|
|
|
for (let i = 0; i < currentRound.length; i += 2) { |
|
if (i + 1 < currentRound.length) { |
|
const player1 = currentRound[i]; |
|
const player2 = currentRound[i + 1]; |
|
|
|
const matchDate = new Date(); |
|
matchDate.setDate(matchDate.getDate() + 7 * matchesThisRound.length); |
|
|
|
matches.push({ |
|
id: Date.now().toString() + i, |
|
player1Id: player1.id, |
|
player1Name: player1.name, |
|
player2Id: player2.id, |
|
player2Name: player2.name, |
|
dateTime: matchDate.toISOString(), |
|
phase: phase, |
|
completed: false |
|
}); |
|
|
|
matchesThisRound.push({ |
|
player1: player1, |
|
player2: player2 |
|
}); |
|
|
|
|
|
nextRound.push(player1.level >= player2.level ? player1 : player2); |
|
} else { |
|
|
|
nextRound.push(currentRound[i]); |
|
} |
|
} |
|
|
|
currentRound = nextRound; |
|
|
|
|
|
if (phase === 'round16') phase = 'quarter'; |
|
else if (phase === 'quarter') phase = 'semi'; |
|
else if (phase === 'semi') phase = 'final'; |
|
} |
|
} else { |
|
|
|
createRoundRobinMatches(players, 'group'); |
|
} |
|
|
|
showToast('Tournoi généré avec succès'); |
|
renderMatches(); |
|
updateStats(); |
|
updateNextMatch(); |
|
saveData(); |
|
} |
|
} |
|
|
|
|
|
function createRoundRobinMatches(groupPlayers, phase) { |
|
|
|
for (let i = 0; i < groupPlayers.length; i++) { |
|
for (let j = i + 1; j < groupPlayers.length; j++) { |
|
const player1 = groupPlayers[i]; |
|
const player2 = groupPlayers[j]; |
|
|
|
const matchDate = new Date(); |
|
matchDate.setDate(matchDate.getDate() + i + j); |
|
|
|
matches.push({ |
|
id: Date.now().toString() + i + j, |
|
player1Id: player1.id, |
|
player1Name: player1.name, |
|
player2Id: player2.id, |
|
player2Name: player2.name, |
|
dateTime: matchDate.toISOString(), |
|
phase: phase, |
|
completed: false |
|
}); |
|
} |
|
} |
|
} |
|
|
|
|
|
function showToast(message, type = 'success') { |
|
toastMessage.textContent = message; |
|
|
|
|
|
if (type === 'success') { |
|
toast.className = 'toast bg-green-500 text-white px-6 py-3 rounded-lg shadow-lg'; |
|
} else if (type === 'error') { |
|
toast.className = 'toast bg-red-500 text-white px-6 py-3 rounded-lg shadow-lg'; |
|
} else { |
|
toast.className = 'toast bg-blue-500 text-white px-6 py-3 rounded-lg shadow-lg'; |
|
} |
|
|
|
|
|
toast.classList.add('show'); |
|
|
|
|
|
setTimeout(() => { |
|
toast.classList.remove('show'); |
|
|
|
</html> |