Initial commit
This commit is contained in:
+230
@@ -0,0 +1,230 @@
|
||||
// Vérification admin
|
||||
window.onload = function() {
|
||||
const user = JSON.parse(localStorage.getItem('currentUser'));
|
||||
if (!user || user.role !== 'admin') {
|
||||
window.location.href = 'index.html';
|
||||
}
|
||||
document.getElementById('adminName').textContent = user.username;
|
||||
loadAdminData();
|
||||
};
|
||||
|
||||
function logout() {
|
||||
localStorage.removeItem('currentUser');
|
||||
window.location.href = 'index.html';
|
||||
}
|
||||
|
||||
function showAdminSection(sectionId) {
|
||||
document.querySelectorAll('.section').forEach(s => s.classList.remove('active'));
|
||||
document.querySelectorAll('.sidebar li').forEach(l => l.classList.remove('active'));
|
||||
document.getElementById(sectionId).classList.add('active');
|
||||
event.target.classList.add('active');
|
||||
|
||||
if (sectionId === 'matches') loadMatchesTable();
|
||||
if (sectionId === 'users') loadUsersTable();
|
||||
if (sectionId === 'results') loadResultsDropdown();
|
||||
}
|
||||
|
||||
function loadAdminData() {
|
||||
document.getElementById('totalMatches').textContent = tournamentMatches.length;
|
||||
document.getElementById('totalPlayers').textContent = Object.keys(playersData).length;
|
||||
document.getElementById('totalUsers').textContent = users.length;
|
||||
|
||||
const allPredictions = JSON.parse(localStorage.getItem('userPredictions') || '[]');
|
||||
document.getElementById('totalPredictions').textContent = allPredictions.length;
|
||||
|
||||
// Populate player selects
|
||||
const playerSelects = ['matchPlayer1', 'matchPlayer2'];
|
||||
playerSelects.forEach(selectId => {
|
||||
const select = document.getElementById(selectId);
|
||||
select.innerHTML = '';
|
||||
Object.values(playersData).forEach(player => {
|
||||
const option = document.createElement('option');
|
||||
option.value = player.id;
|
||||
option.textContent = player.name;
|
||||
select.appendChild(option);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function loadMatchesTable() {
|
||||
const tbody = document.getElementById('matchesTable');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
tournamentMatches.forEach(match => {
|
||||
const p1 = playersData[match.player1];
|
||||
const p2 = playersData[match.player2];
|
||||
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td>${match.round}</td>
|
||||
<td>${p1.name}</td>
|
||||
<td>${p2.name}</td>
|
||||
<td>${new Date(match.date).toLocaleDateString('fr-FR')}</td>
|
||||
<td>${match.status === 'completed' ? 'Terminé' : 'À venir'}</td>
|
||||
<td>
|
||||
<button class="btn-danger" onclick="deleteMatch(${match.id})">Supprimer</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
function loadUsersTable() {
|
||||
const tbody = document.getElementById('usersTable');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
users.forEach(user => {
|
||||
const row = document.createElement('tr');
|
||||
row.innerHTML = `
|
||||
<td>${user.id}</td>
|
||||
<td>${user.username}</td>
|
||||
<td>${user.email}</td>
|
||||
<td>${user.role}</td>
|
||||
<td>${user.points || 0}</td>
|
||||
<td>
|
||||
<button class="btn-danger" onclick="deleteUser(${user.id})">Supprimer</button>
|
||||
</td>
|
||||
`;
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
}
|
||||
|
||||
function addMatch() {
|
||||
const newMatch = {
|
||||
id: Date.now(),
|
||||
round: document.getElementById('matchRound').value,
|
||||
player1: document.getElementById('matchPlayer1').value,
|
||||
player2: document.getElementById('matchPlayer2').value,
|
||||
date: document.getElementById('matchDate').value,
|
||||
court: document.getElementById('matchCourt').value,
|
||||
status: 'upcoming'
|
||||
};
|
||||
|
||||
tournamentMatches.push(newMatch);
|
||||
alert('Match ajouté avec succès!');
|
||||
loadAdminData();
|
||||
loadMatchesTable();
|
||||
}
|
||||
|
||||
function deleteMatch(id) {
|
||||
if (confirm('Êtes-vous sûr de vouloir supprimer ce match?')) {
|
||||
const index = tournamentMatches.findIndex(m => m.id === id);
|
||||
if (index > -1) {
|
||||
tournamentMatches.splice(index, 1);
|
||||
loadMatchesTable();
|
||||
loadAdminData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function addPlayer() {
|
||||
const newPlayer = {
|
||||
id: document.getElementById('playerName').value.toLowerCase().replace(' ', ''),
|
||||
name: document.getElementById('playerName').value,
|
||||
nationality: document.getElementById('playerNationality').value,
|
||||
age: parseInt(document.getElementById('playerAge').value),
|
||||
handedness: document.getElementById('playerHandedness').value,
|
||||
ranking: parseInt(document.getElementById('playerRanking').value),
|
||||
photo: 'https://www.atptour.com/-/media/tennis/players/head-shot/2024/default.png',
|
||||
strengths: ['Nouveau joueur'],
|
||||
weaknesses: ['À définir'],
|
||||
surfaceStats: {
|
||||
clay: { winRate: 0.5, titles: 0 },
|
||||
hard: { winRate: 0.5, titles: 0 },
|
||||
grass: { winRate: 0.5, titles: 0 }
|
||||
},
|
||||
recentForm: ['W', 'L', 'W', 'L', 'W']
|
||||
};
|
||||
|
||||
playersData[newPlayer.id] = newPlayer;
|
||||
alert('Joueur ajouté avec succès!');
|
||||
loadAdminData();
|
||||
}
|
||||
|
||||
function deleteUser(id) {
|
||||
if (confirm('Êtes-vous sûr de vouloir supprimer cet utilisateur?')) {
|
||||
const index = users.findIndex(u => u.id === id);
|
||||
if (index > -1) {
|
||||
users.splice(index, 1);
|
||||
localStorage.setItem('users', JSON.stringify(users));
|
||||
loadUsersTable();
|
||||
loadAdminData();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function loadResultsDropdown() {
|
||||
const select = document.getElementById('resultMatch');
|
||||
select.innerHTML = '';
|
||||
|
||||
tournamentMatches.filter(m => m.status === 'upcoming').forEach(match => {
|
||||
const p1 = playersData[match.player1];
|
||||
const p2 = playersData[match.player2];
|
||||
const option = document.createElement('option');
|
||||
option.value = match.id;
|
||||
option.textContent = `${match.round}: ${p1.name} vs ${p2.name}`;
|
||||
option.dataset.p1 = match.player1;
|
||||
option.dataset.p2 = match.player2;
|
||||
select.appendChild(option);
|
||||
});
|
||||
|
||||
updateWinnerSelect();
|
||||
|
||||
select.addEventListener('change', updateWinnerSelect);
|
||||
}
|
||||
|
||||
function updateWinnerSelect() {
|
||||
const select = document.getElementById('resultMatch');
|
||||
const winnerSelect = document.getElementById('resultWinner');
|
||||
const selectedOption = select.options[select.selectedIndex];
|
||||
|
||||
winnerSelect.innerHTML = '';
|
||||
if (selectedOption.dataset.p1) {
|
||||
const p1 = playersData[selectedOption.dataset.p1];
|
||||
const p2 = playersData[selectedOption.dataset.p2];
|
||||
|
||||
winnerSelect.innerHTML = `
|
||||
<option value="${p1.id}">${p1.name}</option>
|
||||
<option value="${p2.id}">${p2.name}</option>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
function updateResult() {
|
||||
const matchId = parseInt(document.getElementById('resultMatch').value);
|
||||
const winner = document.getElementById('resultWinner').value;
|
||||
const score = document.getElementById('resultScore').value;
|
||||
|
||||
const match = tournamentMatches.find(m => m.id === matchId);
|
||||
if (match) {
|
||||
match.status = 'completed';
|
||||
match.winner = winner;
|
||||
match.score = score;
|
||||
|
||||
// Calculer les points pour les utilisateurs
|
||||
calculateUserPoints(matchId, winner);
|
||||
|
||||
alert('Résultat mis à jour avec succès!');
|
||||
loadAdminData();
|
||||
}
|
||||
}
|
||||
|
||||
function calculateUserPoints(matchId, winner) {
|
||||
const allPredictions = JSON.parse(localStorage.getItem('userPredictions') || '[]');
|
||||
const matchPredictions = allPredictions.filter(p => p.matchId === matchId);
|
||||
|
||||
matchPredictions.forEach(pred => {
|
||||
const user = users.find(u => u.id === pred.userId);
|
||||
if (user && pred.predictedWinner === winner) {
|
||||
user.points = (user.points || 0) + 50; // 50 points pour un pronostic juste
|
||||
|
||||
// Mettre à jour localStorage
|
||||
const currentUser = JSON.parse(localStorage.getItem('currentUser'));
|
||||
if (currentUser && currentUser.id === user.id) {
|
||||
localStorage.setItem('currentUser', JSON.stringify(user));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
localStorage.setItem('users', JSON.stringify(users));
|
||||
}
|
||||
+162
@@ -0,0 +1,162 @@
|
||||
const API_BASE_URL = 'http://localhost/mon-petit-pari/api';
|
||||
|
||||
// Gestion du token
|
||||
function getToken() {
|
||||
return localStorage.getItem('authToken');
|
||||
}
|
||||
|
||||
function setToken(token) {
|
||||
localStorage.setItem('authToken', token);
|
||||
}
|
||||
|
||||
function removeToken() {
|
||||
localStorage.removeItem('authToken');
|
||||
localStorage.removeItem('currentUser');
|
||||
}
|
||||
|
||||
// Fonction pour faire des requêtes API
|
||||
async function apiCall(endpoint, method = 'GET', data = null) {
|
||||
const options = {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
const token = getToken();
|
||||
if (token) {
|
||||
options.headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
if (data && (method === 'POST' || method === 'PUT')) {
|
||||
options.body = JSON.stringify(data);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/${endpoint}`, options);
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(result.error || 'Erreur serveur');
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('API Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Inscription
|
||||
async function register() {
|
||||
const username = document.getElementById('regUsername').value;
|
||||
const email = document.getElementById('regEmail').value;
|
||||
const password = document.getElementById('regPassword').value;
|
||||
|
||||
if (!username || !email || !password) {
|
||||
alert('Veuillez remplir tous les champs');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await apiCall('auth.php?action=register', 'POST', {
|
||||
username,
|
||||
email,
|
||||
password
|
||||
});
|
||||
|
||||
setToken(result.token);
|
||||
localStorage.setItem('currentUser', JSON.stringify(result.user));
|
||||
|
||||
alert('Compte créé avec succès!');
|
||||
closeModal();
|
||||
|
||||
if (result.user.role === 'admin') {
|
||||
window.location.href = 'admin.html';
|
||||
} else {
|
||||
window.location.href = 'dashboard.html';
|
||||
}
|
||||
} catch (error) {
|
||||
alert(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Connexion
|
||||
async function login() {
|
||||
const username = document.getElementById('loginUsername').value;
|
||||
const password = document.getElementById('loginPassword').value;
|
||||
|
||||
if (!username || !password) {
|
||||
alert('Veuillez remplir tous les champs');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await apiCall('auth.php?action=login', 'POST', {
|
||||
username,
|
||||
password
|
||||
});
|
||||
|
||||
setToken(result.token);
|
||||
localStorage.setItem('currentUser', JSON.stringify(result.user));
|
||||
|
||||
if (result.user.role === 'admin') {
|
||||
window.location.href = 'admin.html';
|
||||
} else {
|
||||
window.location.href = 'dashboard.html';
|
||||
}
|
||||
} catch (error) {
|
||||
alert(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Déconnexion
|
||||
function logout() {
|
||||
removeToken();
|
||||
window.location.href = 'index.html';
|
||||
}
|
||||
|
||||
// Afficher/Masquer le modal
|
||||
function showModal(type) {
|
||||
document.getElementById('authModal').style.display = 'block';
|
||||
if (type === 'register') {
|
||||
toggleAuth('register');
|
||||
}
|
||||
}
|
||||
|
||||
function closeModal() {
|
||||
document.getElementById('authModal').style.display = 'none';
|
||||
}
|
||||
|
||||
function toggleAuth(type) {
|
||||
if (type === 'login') {
|
||||
document.getElementById('loginForm').style.display = 'block';
|
||||
document.getElementById('registerForm').style.display = 'none';
|
||||
} else {
|
||||
document.getElementById('loginForm').style.display = 'none';
|
||||
document.getElementById('registerForm').style.display = 'block';
|
||||
}
|
||||
}
|
||||
|
||||
// Vérifier si l'utilisateur est connecté au chargement
|
||||
window.onload = function() {
|
||||
const token = getToken();
|
||||
const user = localStorage.getItem('currentUser');
|
||||
|
||||
if (token && user) {
|
||||
const userData = JSON.parse(user);
|
||||
if (userData.role === 'admin') {
|
||||
window.location.href = 'admin.html';
|
||||
} else {
|
||||
window.location.href = 'dashboard.html';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Fermer modal en cliquant dehors
|
||||
window.onclick = function(event) {
|
||||
const modal = document.getElementById('authModal');
|
||||
if (event.target === modal) {
|
||||
closeModal();
|
||||
}
|
||||
}
|
||||
+333
@@ -0,0 +1,333 @@
|
||||
// Données basées sur les sources GitHub Jeff Sackmann et API tennis
|
||||
const playersData = {
|
||||
"alcaraz": {
|
||||
id: "alcaraz",
|
||||
name: "Carlos Alcaraz",
|
||||
nationality: "Espagne",
|
||||
age: 23,
|
||||
handedness: "Droitier",
|
||||
photo: "https://www.atptour.com/-/media/tennis/players/head-shot/2024/alcaraz_head_24.png",
|
||||
ranking: 2,
|
||||
points: 7010,
|
||||
strengths: ["Coups droits explosifs", "Drop shots", "Mental", "Vitesse"],
|
||||
weaknesses: ["Service parfois irrégulier", "Revers en slice"],
|
||||
surfaceStats: {
|
||||
clay: { winRate: 0.82, titles: 5 },
|
||||
hard: { winRate: 0.78, titles: 3 },
|
||||
grass: { winRate: 0.71, titles: 1 }
|
||||
},
|
||||
recentForm: ["W", "W", "L", "W", "W"]
|
||||
},
|
||||
"sinner": {
|
||||
id: "sinner",
|
||||
name: "Jannik Sinner",
|
||||
nationality: "Italie",
|
||||
age: 24,
|
||||
handedness: "Droitier",
|
||||
photo: "https://www.atptour.com/-/media/tennis/players/head-shot/2024/sinner_head_24.png",
|
||||
ranking: 1,
|
||||
points: 8770,
|
||||
strengths: ["Fond de court", "Précision", "Physique", "Revers à deux mains"],
|
||||
weaknesses: ["Montée au filet", "Variété de jeu"],
|
||||
surfaceStats: {
|
||||
clay: { winRate: 0.79, titles: 2 },
|
||||
hard: { winRate: 0.85, titles: 6 },
|
||||
grass: { winRate: 0.68, titles: 0 }
|
||||
},
|
||||
recentForm: ["W", "W", "W", "L", "W"]
|
||||
},
|
||||
"nadal": {
|
||||
id: "nadal",
|
||||
name: "Rafael Nadal",
|
||||
nationality: "Espagne",
|
||||
age: 40,
|
||||
handedness: "Gaucher",
|
||||
photo: "https://www.atptour.com/-/media/tennis/players/head-shot/2024/nadal_head_24.png",
|
||||
ranking: 154,
|
||||
points: 380,
|
||||
strengths: ["Topspin lourd", "Mental d'acier", "Terre battue", "Défense"],
|
||||
weaknesses: ["Âge", "Blessures récurrentes", "Mobilité réduite"],
|
||||
surfaceStats: {
|
||||
clay: { winRate: 0.95, titles: 14 },
|
||||
hard: { winRate: 0.72, titles: 4 },
|
||||
grass: { winRate: 0.65, titles: 0 }
|
||||
},
|
||||
recentForm: ["L", "W", "L", "L", "W"]
|
||||
},
|
||||
"djokovic": {
|
||||
id: "djokovic",
|
||||
name: "Novak Djokovic",
|
||||
nationality: "Serbie",
|
||||
age: 39,
|
||||
handedness: "Droitier",
|
||||
photo: "https://www.atptour.com/-/media/tennis/players/head-shot/2024/djokovic_head_24.png",
|
||||
ranking: 7,
|
||||
points: 3910,
|
||||
strengths: ["Retour de service", "Flexibilité", "Mental", "Tous surfaces"],
|
||||
weaknesses: ["Pression du public", "Âge"],
|
||||
surfaceStats: {
|
||||
clay: { winRate: 0.80, titles: 2 },
|
||||
hard: { winRate: 0.84, titles: 10 },
|
||||
grass: { winRate: 0.86, titles: 7 }
|
||||
},
|
||||
recentForm: ["W", "L", "W", "W", "L"]
|
||||
},
|
||||
"medvedev": {
|
||||
id: "medvedev",
|
||||
name: "Daniil Medvedev",
|
||||
nationality: "Russie",
|
||||
age: 30,
|
||||
handedness: "Droitier",
|
||||
photo: "https://www.atptour.com/-/media/tennis/players/head-shot/2024/medvedev_head_24.png",
|
||||
ranking: 4,
|
||||
points: 5230,
|
||||
strengths: ["Service", "Retour", "Tactique", "Durée de rallye"],
|
||||
weaknesses: ["Terre battue", "Impatience"],
|
||||
surfaceStats: {
|
||||
clay: { winRate: 0.65, titles: 0 },
|
||||
hard: { winRate: 0.82, titles: 6 },
|
||||
grass: { winRate: 0.62, titles: 0 }
|
||||
},
|
||||
recentForm: ["L", "W", "W", "L", "W"]
|
||||
},
|
||||
"zverev": {
|
||||
id: "zverev",
|
||||
name: "Alexander Zverev",
|
||||
nationality: "Allemagne",
|
||||
age: 29,
|
||||
handedness: "Droitier",
|
||||
photo: "https://www.atptour.com/-/media/tennis/players/head-shot/2024/zverev_head_24.png",
|
||||
ranking: 3,
|
||||
points: 6315,
|
||||
strengths: ["Service puissant", "Revers", "Physique"],
|
||||
weaknesses: ["Nerfs en Grand Chelem", "Double fautes"],
|
||||
surfaceStats: {
|
||||
clay: { winRate: 0.76, titles: 2 },
|
||||
hard: { winRate: 0.75, titles: 5 },
|
||||
grass: { winRate: 0.64, titles: 0 }
|
||||
},
|
||||
recentForm: ["W", "W", "L", "W", "W"]
|
||||
},
|
||||
"rublev": {
|
||||
id: "rublev",
|
||||
name: "Andrey Rublev",
|
||||
nationality: "Russie",
|
||||
age: 28,
|
||||
handedness: "Droitier",
|
||||
photo: "https://www.atptour.com/-/media/tennis/players/head-shot/2024/rublev_head_24.png",
|
||||
ranking: 6,
|
||||
points: 4420,
|
||||
strengths: ["Coups droits", "Agressivité", "Énergie"],
|
||||
weaknesses: ["Contrôle émotionnel", "Consistance"],
|
||||
surfaceStats: {
|
||||
clay: { winRate: 0.74, titles: 1 },
|
||||
hard: { winRate: 0.76, titles: 4 },
|
||||
grass: { winRate: 0.58, titles: 0 }
|
||||
},
|
||||
recentForm: ["L", "L", "W", "W", "L"]
|
||||
},
|
||||
"ruud": {
|
||||
id: "ruud",
|
||||
name: "Casper Ruud",
|
||||
nationality: "Norvège",
|
||||
age: 27,
|
||||
handedness: "Droitier",
|
||||
photo: "https://www.atptour.com/-/media/tennis/players/head-shot/2024/ruud_head_24.png",
|
||||
ranking: 8,
|
||||
points: 3855,
|
||||
strengths: ["Revers lifté", "Terre battue", "Endurance"],
|
||||
weaknesses: ["Service", "Niveau sur autres surfaces"],
|
||||
surfaceStats: {
|
||||
clay: { winRate: 0.78, titles: 3 },
|
||||
hard: { winRate: 0.65, titles: 0 },
|
||||
grass: { winRate: 0.55, titles: 0 }
|
||||
},
|
||||
recentForm: ["W", "L", "W", "L", "W"]
|
||||
}
|
||||
};
|
||||
|
||||
// Matches du tournoi (Tableau principal simulé)
|
||||
const tournamentMatches = [
|
||||
{
|
||||
id: 1,
|
||||
round: "Finale",
|
||||
player1: "alcaraz",
|
||||
player2: "sinner",
|
||||
date: "2026-06-07",
|
||||
court: "Court Philippe-Chatrier",
|
||||
status: "upcoming"
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
round: "Demi-finale",
|
||||
player1: "alcaraz",
|
||||
player2: "zverev",
|
||||
date: "2026-06-05",
|
||||
court: "Court Philippe-Chatrier",
|
||||
status: "completed",
|
||||
winner: "alcaraz",
|
||||
score: "6-3, 6-2, 6-4"
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
round: "Demi-finale",
|
||||
player1: "sinner",
|
||||
player2: "nadal",
|
||||
date: "2026-06-05",
|
||||
court: "Court Philippe-Chatrier",
|
||||
status: "completed",
|
||||
winner: "sinner",
|
||||
score: "7-6, 6-4, 3-6, 6-3"
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
round: "Quart de finale",
|
||||
player1: "alcaraz",
|
||||
player2: "rublev",
|
||||
date: "2026-06-03",
|
||||
court: "Court Philippe-Chatrier",
|
||||
status: "completed",
|
||||
winner: "alcaraz",
|
||||
score: "6-4, 6-2, 6-3"
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
round: "Quart de finale",
|
||||
player1: "zverev",
|
||||
player2: "medvedev",
|
||||
date: "2026-06-03",
|
||||
court: "Court Suzanne-Lenglen",
|
||||
status: "completed",
|
||||
winner: "zverev",
|
||||
score: "7-6, 6-4, 6-7, 6-3"
|
||||
}
|
||||
];
|
||||
|
||||
// Historique des confrontations (H2H)
|
||||
const headToHead = {
|
||||
"alcaraz-sinner": {
|
||||
total: 11,
|
||||
alcaraz: 5,
|
||||
sinner: 6,
|
||||
clay: { alcaraz: 3, sinner: 1 },
|
||||
lastMeetings: [
|
||||
{ date: "2025-11-15", tournament: "ATP Finals", winner: "sinner", score: "6-3, 6-4" },
|
||||
{ date: "2025-09-10", tournament: "US Open", winner: "alcaraz", score: "6-2, 6-4, 7-6" }
|
||||
]
|
||||
},
|
||||
"alcaraz-nadal": {
|
||||
total: 8,
|
||||
alcaraz: 4,
|
||||
nadel: 4,
|
||||
clay: { alcaraz: 2, nadal: 2 },
|
||||
lastMeetings: [
|
||||
{ date: "2025-05-15", tournament: "Rome Masters", winner: "alcaraz", score: "6-4, 6-3" }
|
||||
]
|
||||
},
|
||||
"sinner-nadal": {
|
||||
total: 5,
|
||||
sinner: 3,
|
||||
nadal: 2,
|
||||
clay: { sinner: 1, nadal: 1 },
|
||||
lastMeetings: [
|
||||
{ date: "2025-06-02", tournament: "Roland Garros", winner: "sinner", score: "6-3, 6-4, 6-2" }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
// Fonction pour calculer les probabilités
|
||||
function calculateWinProbability(player1Id, player2Id, surface = 'clay') {
|
||||
const p1 = playersData[player1Id];
|
||||
const p2 = playersData[player2Id];
|
||||
|
||||
let prob1 = 50;
|
||||
let prob2 = 50;
|
||||
|
||||
// Facteur ranking
|
||||
const rankingDiff = p2.ranking - p1.ranking;
|
||||
prob1 += rankingDiff * 2;
|
||||
prob2 -= rankingDiff * 2;
|
||||
|
||||
// Facteur surface
|
||||
const p1Surface = p1.surfaceStats[surface].winRate * 100;
|
||||
const p2Surface = p2.surfaceStats[surface].winRate * 100;
|
||||
const surfaceDiff = p1Surface - p2Surface;
|
||||
prob1 += surfaceDiff * 0.5;
|
||||
prob2 -= surfaceDiff * 0.5;
|
||||
|
||||
// Facteur forme récente
|
||||
const p1Form = p1.recentForm.filter(r => r === 'W').length / 5;
|
||||
const p2Form = p2.recentForm.filter(r => r === 'W').length / 5;
|
||||
prob1 += (p1Form - p2Form) * 10;
|
||||
prob2 -= (p1Form - p2Form) * 10;
|
||||
|
||||
// H2H
|
||||
const h2hKey = `${player1Id}-${player2Id}`;
|
||||
const h2hReverse = `${player2Id}-${player1Id}`;
|
||||
if (headToHead[h2hKey]) {
|
||||
const h2h = headToHead[h2hKey];
|
||||
const h2hClay = h2h.clay ? (h2h.alcaraz || h2h.sinner) : h2h.alcaraz;
|
||||
const totalClay = h2h.clay ? (h2h.clay.alcaraz + h2h.clay.sinner) : h2h.total;
|
||||
if (totalClay > 0) {
|
||||
const h2hAdvantage = (h2hClay / totalClay - 0.5) * 20;
|
||||
prob1 += h2hAdvantage;
|
||||
prob2 -= h2hAdvantage;
|
||||
}
|
||||
}
|
||||
|
||||
// Normalisation
|
||||
const total = prob1 + prob2;
|
||||
prob1 = Math.round((prob1 / total) * 100);
|
||||
prob2 = 100 - prob1;
|
||||
|
||||
return {
|
||||
player1: Math.max(10, Math.min(90, prob1)),
|
||||
player2: Math.max(10, Math.min(90, prob2))
|
||||
};
|
||||
}
|
||||
|
||||
// Fonction pour obtenir les atouts/faiblesses face à face
|
||||
function getMatchupAnalysis(player1Id, player2Id) {
|
||||
const p1 = playersData[player1Id];
|
||||
const p2 = playersData[player2Id];
|
||||
|
||||
const analysis = {
|
||||
player1Advantages: [],
|
||||
player1Disadvantages: [],
|
||||
player2Advantages: [],
|
||||
player2Disadvantages: []
|
||||
};
|
||||
|
||||
// Comparaison des forces
|
||||
p1.strengths.forEach(strength => {
|
||||
if (!p2.strengths.includes(strength) && !p2.weaknesses.includes(strength)) {
|
||||
analysis.player1Advantages.push(strength);
|
||||
}
|
||||
});
|
||||
|
||||
p2.strengths.forEach(strength => {
|
||||
if (!p1.strengths.includes(strength) && !p1.weaknesses.includes(strength)) {
|
||||
analysis.player2Advantages.push(strength);
|
||||
}
|
||||
});
|
||||
|
||||
// Exploitation des faiblesses
|
||||
p1.weaknesses.forEach(weakness => {
|
||||
if (p2.strengths.some(s => s.toLowerCase().includes(weakness.toLowerCase().split(' ')[0]))) {
|
||||
analysis.player2Advantages.push(`Exploite: ${weakness}`);
|
||||
}
|
||||
});
|
||||
|
||||
p2.weaknesses.forEach(weakness => {
|
||||
if (p1.strengths.some(s => s.toLowerCase().includes(weakness.toLowerCase().split(' ')[0]))) {
|
||||
analysis.player1Advantages.push(`Exploite: ${weakness}`);
|
||||
}
|
||||
});
|
||||
|
||||
return analysis;
|
||||
}
|
||||
|
||||
// Export pour utilisation dans d'autres fichiers
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = { playersData, tournamentMatches, headToHead, calculateWinProbability, getMatchupAnalysis };
|
||||
}
|
||||
@@ -0,0 +1,318 @@
|
||||
const API_BASE_URL = 'http://localhost/mon-petit-pari/api';
|
||||
let currentUser = null;
|
||||
let currentMatch = null;
|
||||
|
||||
// Fonction API
|
||||
async function apiCall(endpoint, method = 'GET', data = null) {
|
||||
const options = {
|
||||
method: method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
const token = localStorage.getItem('authToken');
|
||||
if (token) {
|
||||
options.headers['Authorization'] = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
if (data && (method === 'POST' || method === 'PUT')) {
|
||||
options.body = JSON.stringify(data);
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`${API_BASE_URL}/${endpoint}`, options);
|
||||
const result = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(result.error || 'Erreur serveur');
|
||||
}
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('API Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Vérification de la session au chargement
|
||||
window.onload = async function() {
|
||||
const token = localStorage.getItem('authToken');
|
||||
if (!token) {
|
||||
window.location.href = 'index.html';
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const result = await apiCall('auth.php?action=me');
|
||||
currentUser = result.user;
|
||||
document.getElementById('userName').textContent = currentUser.username;
|
||||
document.getElementById('userPoints').textContent = `${currentUser.points} pts`;
|
||||
|
||||
await loadMatches();
|
||||
await loadPlayers();
|
||||
await loadLeaderboard();
|
||||
await loadStats();
|
||||
} catch (error) {
|
||||
alert('Session expirée. Veuillez vous reconnecter.');
|
||||
localStorage.removeItem('authToken');
|
||||
localStorage.removeItem('currentUser');
|
||||
window.location.href = 'index.html';
|
||||
}
|
||||
};
|
||||
|
||||
function logout() {
|
||||
localStorage.removeItem('authToken');
|
||||
localStorage.removeItem('currentUser');
|
||||
window.location.href = 'index.html';
|
||||
}
|
||||
|
||||
function showSection(sectionId) {
|
||||
document.querySelectorAll('.section').forEach(s => s.classList.remove('active'));
|
||||
document.querySelectorAll('.sidebar li').forEach(l => l.classList.remove('active'));
|
||||
document.getElementById(sectionId).classList.add('active');
|
||||
event.target.classList.add('active');
|
||||
}
|
||||
|
||||
// Chargement des matches depuis la BDD
|
||||
async function loadMatches() {
|
||||
try {
|
||||
const result = await apiCall('matches.php');
|
||||
const container = document.getElementById('matchesList');
|
||||
container.innerHTML = '';
|
||||
|
||||
result.matches.forEach(match => {
|
||||
const matchCard = document.createElement('div');
|
||||
matchCard.className = 'match-card';
|
||||
matchCard.onclick = () => openPredictionModal(match);
|
||||
|
||||
matchCard.innerHTML = `
|
||||
<div class="player-info">
|
||||
<img src="${match.player1.photo}" alt="${match.player1.name}" class="player-photo" onerror="this.src='https://via.placeholder.com/60?text=${encodeURIComponent(match.player1.name.charAt(0))}'">
|
||||
<div class="player-details">
|
||||
<h3>${match.player1.name}</h3>
|
||||
<p>${match.player1.nationality} - ${match.player1.handedness}</p>
|
||||
<p>Ranking: #${match.player1.ranking}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="vs">VS</div>
|
||||
<div class="player-info" style="flex-direction: row-reverse; text-align: right;">
|
||||
<img src="${match.player2.photo}" alt="${match.player2.name}" class="player-photo" onerror="this.src='https://via.placeholder.com/60?text=${encodeURIComponent(match.player2.name.charAt(0))}'">
|
||||
<div class="player-details">
|
||||
<h3>${match.player2.name}</h3>
|
||||
<p>${match.player2.nationality} - ${match.player2.handedness}</p>
|
||||
<p>Ranking: #${match.player2.ranking}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="match-info">
|
||||
<span class="round">${match.round}</span>
|
||||
<p class="date">${new Date(match.date).toLocaleDateString('fr-FR')}</p>
|
||||
<p>${match.court}</p>
|
||||
${match.status === 'completed' ? `<p class="score">${match.score}</p>` : ''}
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.appendChild(matchCard);
|
||||
});
|
||||
} catch (error) {
|
||||
alert('Erreur lors du chargement des matches: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Chargement des joueurs depuis la BDD
|
||||
async function loadPlayers() {
|
||||
try {
|
||||
const result = await apiCall('players.php');
|
||||
const container = document.getElementById('playersGrid');
|
||||
container.innerHTML = '';
|
||||
|
||||
result.players.forEach(player => {
|
||||
const card = document.createElement('div');
|
||||
card.className = 'player-card';
|
||||
|
||||
const strengthsHtml = player.strengths.map(s => `<span class="tag strength">${s}</span>`).join('');
|
||||
const weaknessesHtml = player.weaknesses.map(w => `<span class="tag weakness">${w}</span>`).join('');
|
||||
|
||||
card.innerHTML = `
|
||||
<div class="player-header">
|
||||
<img src="${player.photo_url}" alt="${player.name}" onerror="this.src='https://via.placeholder.com/120?text=${encodeURIComponent(player.name.charAt(0))}'">
|
||||
<h3>${player.name}</h3>
|
||||
<p>#${player.ranking} Mondial</p>
|
||||
</div>
|
||||
<div class="player-body">
|
||||
<div class="stat-row">
|
||||
<span>Âge</span>
|
||||
<strong>${player.age} ans</strong>
|
||||
</div>
|
||||
<div class="stat-row">
|
||||
<span>Nationalité</span>
|
||||
<strong>${player.nationality}</strong>
|
||||
</div>
|
||||
<div class="stat-row">
|
||||
<span>Handedness</span>
|
||||
<strong>${player.handedness}</strong>
|
||||
</div>
|
||||
<div class="stat-row">
|
||||
<span>Terre battue</span>
|
||||
<strong>${(player.clay_win_rate * 100).toFixed(0)}%</strong>
|
||||
</div>
|
||||
<div class="strengths">
|
||||
<h4>Points forts</h4>
|
||||
${strengthsHtml}
|
||||
</div>
|
||||
<div class="weaknesses">
|
||||
<h4>Points faibles</h4>
|
||||
${weaknessesHtml}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
container.appendChild(card);
|
||||
});
|
||||
} catch (error) {
|
||||
alert('Erreur lors du chargement des joueurs: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Modal de pronostic avec analyse depuis la BDD
|
||||
async function openPredictionModal(match) {
|
||||
if (match.status === 'completed') return;
|
||||
|
||||
currentMatch = match;
|
||||
|
||||
try {
|
||||
// Récupérer l'analyse du matchup
|
||||
const result = await apiCall(`players.php?action=matchup&player1=${match.player1.id}&player2=${match.player2.id}`);
|
||||
|
||||
const modal = document.getElementById('predictionModal');
|
||||
const details = document.getElementById('matchDetails');
|
||||
|
||||
details.innerHTML = `
|
||||
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; margin-bottom: 2rem;">
|
||||
<div style="text-align: center;">
|
||||
<img src="${match.player1.photo}" style="width: 100px; height: 100px; border-radius: 50%; border: 3px solid #e85d04;" onerror="this.src='https://via.placeholder.com/100?text=${encodeURIComponent(match.player1.name.charAt(0))}'">
|
||||
<h3>${match.player1.name}</h3>
|
||||
<p>${match.player1.nationality}</p>
|
||||
</div>
|
||||
<div style="text-align: center;">
|
||||
<img src="${match.player2.photo}" style="width: 100px; height: 100px; border-radius: 50%; border: 3px solid #e85d04;" onerror="this.src='https://via.placeholder.com/100?text=${encodeURIComponent(match.player2.name.charAt(0))}'">
|
||||
<h3>${match.player2.name}</h3>
|
||||
<p>${match.player2.nationality}</p>
|
||||
</div>
|
||||
</div>
|
||||
<p style="text-align: center; color: #666;">${match.round} - ${match.court}</p>
|
||||
`;
|
||||
|
||||
// Afficher les probabilités
|
||||
document.getElementById('prob1').style.width = `${result.probabilities.player1}%`;
|
||||
document.getElementById('prob2').style.width = `${result.probabilities.player2}%`;
|
||||
document.getElementById('probText1').textContent = `${result.probabilities.player1}%`;
|
||||
document.getElementById('probText2').textContent = `${result.probabilities.player2}%`;
|
||||
|
||||
// Afficher l'analyse
|
||||
const analysisDiv = document.getElementById('matchupAnalysis');
|
||||
analysisDiv.innerHTML = `
|
||||
<h3>Analyse du Matchup</h3>
|
||||
<div class="analysis-grid">
|
||||
<div class="advantage">
|
||||
<h4>Avantages ${match.player1.name}</h4>
|
||||
<ul>
|
||||
${result.analysis.player1_advantages.map(a => `<li>${a}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
<div class="advantage">
|
||||
<h4>Avantages ${match.player2.name}</h4>
|
||||
<ul>
|
||||
${result.analysis.player2_advantages.map(a => `<li>${a}</li>`).join('')}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
modal.style.display = 'block';
|
||||
} catch (error) {
|
||||
alert('Erreur lors du chargement de l\'analyse: ' + error.message);
|
||||
}
|
||||
}
|
||||
|
||||
function closePredictionModal() {
|
||||
document.getElementById('predictionModal').style.display = 'none';
|
||||
currentMatch = null;
|
||||
}
|
||||
|
||||
// Faire un pronostic via l'API
|
||||
async function makePrediction(playerNum) {
|
||||
if (!currentMatch || !currentUser) return;
|
||||
|
||||
const winnerId = playerNum === 1 ? currentMatch.player1.id : currentMatch.player2.id;
|
||||
const winnerName = playerNum === 1 ? currentMatch.player1.name : currentMatch.player2.name;
|
||||
|
||||
try {
|
||||
const result = await apiCall('predictions.php', 'POST', {
|
||||
match_id: currentMatch.id,
|
||||
predicted_winner_id: winnerId
|
||||
});
|
||||
|
||||
// Mettre à jour les points affichés
|
||||
currentUser.points += result.points_earned;
|
||||
document.getElementById('userPoints').textContent = `${currentUser.points} pts`;
|
||||
localStorage.setItem('currentUser', JSON.stringify(currentUser));
|
||||
|
||||
alert(`Pronostic enregistré! Vous avez choisi ${winnerName}\n+${result.points_earned} points`);
|
||||
closePredictionModal();
|
||||
await loadStats();
|
||||
await loadLeaderboard();
|
||||
} catch (error) {
|
||||
alert(error.message);
|
||||
}
|
||||
}
|
||||
|
||||
// Charger le classement depuis la BDD
|
||||
async function loadLeaderboard() {
|
||||
try {
|
||||
const result = await apiCall('predictions.php?action=leaderboard');
|
||||
const tbody = document.getElementById('leaderboardBody');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
result.leaderboard.forEach(user => {
|
||||
const row = document.createElement('tr');
|
||||
const isCurrentUser = user.user_id === currentUser.id;
|
||||
|
||||
row.innerHTML = `
|
||||
<td>${user.rank}</td>
|
||||
<td>${user.username} ${isCurrentUser ? '(Vous)' : ''}</td>
|
||||
<td>${user.points}</td>
|
||||
<td>${user.correct_predictions}</td>
|
||||
`;
|
||||
|
||||
if (isCurrentUser) {
|
||||
row.style.background = '#faf3e0';
|
||||
row.style.fontWeight = 'bold';
|
||||
}
|
||||
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Erreur leaderboard:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Charger les stats utilisateur depuis la BDD
|
||||
async function loadStats() {
|
||||
try {
|
||||
const result = await apiCall('predictions.php?action=stats');
|
||||
document.getElementById('totalPredictions').textContent = result.stats.total_predictions;
|
||||
document.getElementById('successRate').textContent = `${result.stats.success_rate}%`;
|
||||
document.getElementById('bestStreak').textContent = result.stats.correct_predictions;
|
||||
} catch (error) {
|
||||
console.error('Erreur stats:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Fermer modal en cliquant dehors
|
||||
window.onclick = function(event) {
|
||||
const modal = document.getElementById('predictionModal');
|
||||
if (event.target === modal) {
|
||||
closePredictionModal();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user