Actualiser js/prediction.js

This commit is contained in:
2026-07-04 14:02:05 +02:00
parent 59f8bbefeb
commit b202e71ea9
+161 -214
View File
@@ -1,238 +1,217 @@
const API_BASE_URL = window.location.origin + '/api'; // Données utilisateurs locales persistantes pour éviter les pannes d'API au chargement local
let currentUser = null; let currentUser = JSON.parse(localStorage.getItem('currentUser')) || {
username: "Pronostiqueur Pro 2026",
points: 320,
predictionsCount: 3,
successRate: 66
};
let currentMatch = null; let currentMatch = null;
let currentCategoryFilter = 'all';
// Fonction API window.onload = function() {
async function apiCall(endpoint, method = 'GET', data = null) { initDashboard();
const options = { };
method: method,
headers: {
'Content-Type': 'application/json'
}
};
const token = localStorage.getItem('authToken'); function initDashboard() {
if (token) { // Affichage des informations de l'utilisateur connecté
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('userName').textContent = currentUser.username;
document.getElementById('userPoints').textContent = `${currentUser.points} pts`; document.getElementById('userPoints').textContent = `${currentUser.points} pts`;
await loadMatches(); // Rendu initial des sections
await loadPlayers(); loadMatches();
await loadLeaderboard(); loadPlayers();
await loadStats(); loadLeaderboard();
} catch (error) { loadStats();
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) { function showSection(sectionId) {
document.querySelectorAll('.section').forEach(s => s.classList.remove('active')); document.querySelectorAll('.section').forEach(s => s.classList.remove('active'));
document.querySelectorAll('.sidebar li').forEach(l => l.classList.remove('active')); document.querySelectorAll('.sidebar li').forEach(l => l.classList.remove('active'));
document.getElementById(sectionId).classList.add('active'); document.getElementById(sectionId).classList.add('active');
event.target.classList.add('active'); document.getElementById(`tab-${sectionId}`).classList.add('active');
} }
// Chargement des matches depuis la BDD // --- FILTRES ET ENREGISTREMENT DES MATCHES ---
async function loadMatches() { function filterMatchesCategory(category) {
try { currentCategoryFilter = category;
const result = await apiCall('matches.php'); document.querySelectorAll('.filter-btn').forEach(btn => btn.classList.remove('active'));
if(category === 'all') document.getElementById('btn-all-matches').classList.add('active');
if(category === 'Messieurs') document.getElementById('btn-mens-matches').classList.add('active');
if(category === 'Dames') document.getElementById('btn-womens-matches').classList.add('active');
loadMatches();
}
function loadMatches() {
const container = document.getElementById('matchesList'); const container = document.getElementById('matchesList');
if(!container) return;
container.innerHTML = ''; container.innerHTML = '';
result.matches.forEach(match => { const filteredMatches = tournamentMatches.filter(match => {
return currentCategoryFilter === 'all' || match.gender === currentCategoryFilter;
});
filteredMatches.forEach(match => {
const p1 = playersData[match.player1];
const p2 = playersData[match.player2];
if(!p1 || !p2) return;
const matchCard = document.createElement('div'); const matchCard = document.createElement('div');
matchCard.className = 'match-card'; matchCard.className = 'match-card';
matchCard.onclick = () => openPredictionModal(match); matchCard.onclick = () => openPredictionModal(match);
const badgeColor = match.gender === 'Messieurs' ? '#e85d04' : '#0077b6';
matchCard.innerHTML = ` matchCard.innerHTML = `
<div class="player-info"> <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))}'"> <img src="${p1.photo}" alt="${p1.name}" class="player-photo">
<div class="player-details"> <div class="player-details">
<h3>${match.player1.name}</h3> <h3>${p1.name}</h3>
<p>${match.player1.nationality} - ${match.player1.handedness}</p> <p>${p1.nationality} ${p1.handedness}</p>
<p>Ranking: #${match.player1.ranking}</p> <p>Classement : #${p1.ranking}</p>
</div> </div>
</div> </div>
<div class="vs">VS</div> <div class="vs" style="color: ${badgeColor}">VS</div>
<div class="player-info" style="flex-direction: row-reverse; text-align: right;"> <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))}'"> <img src="${p2.photo}" alt="${p2.name}" class="player-photo">
<div class="player-details"> <div class="player-details">
<h3>${match.player2.name}</h3> <h3>${p2.name}</h3>
<p>${match.player2.nationality} - ${match.player2.handedness}</p> <p>${p2.nationality} ${p2.handedness}</p>
<p>Ranking: #${match.player2.ranking}</p> <p>Classement : #${p2.ranking}</p>
</div> </div>
</div> </div>
<div class="match-info"> <div class="match-info">
<span class="round">${match.round}</span> <span class="round" style="background: ${badgeColor}">${match.round}</span>
<p class="date">${new Date(match.date).toLocaleDateString('fr-FR')}</p> <p class="date">${new Date(match.date).toLocaleDateString('fr-FR', {day: 'numeric', month: 'long'})}</p>
<p>${match.court}</p> <p style="font-size:0.8rem; color:#888;">${match.court}</p>
${match.status === 'completed' ? `<p class="score">${match.score}</p>` : ''} ${match.status === 'completed' ? `<p class="score" style="font-weight:bold; color:#28a745; margin-top:5px;">${match.score}</p>` : '<p style="color:#e85d04; font-size:0.85rem; font-weight:600;">👉 Pronostiquer</p>'}
</div> </div>
`; `;
container.appendChild(matchCard); container.appendChild(matchCard);
}); });
} catch (error) {
alert('Erreur lors du chargement des matches: ' + error.message);
}
} }
// Chargement des joueurs depuis la BDD // --- MOTEUR DE RECHERCHE ET RENDU DES JOUEURS ---
async function loadPlayers() { function handlePlayerSearch() {
try { loadPlayers();
const result = await apiCall('player.php'); }
function loadPlayers() {
const container = document.getElementById('playersGrid'); const container = document.getElementById('playersGrid');
if(!container) return;
container.innerHTML = ''; container.innerHTML = '';
result.players.forEach(player => { const searchQuery = document.getElementById('playerSearch').value.toLowerCase();
const genderFilter = document.getElementById('genderFilter').value;
Object.keys(playersData).forEach(key => {
const player = playersData[key];
// Validation des filtres de recherche croisés
const matchesSearch = player.name.toLowerCase().includes(searchQuery) || player.nationality.toLowerCase().includes(searchQuery);
const matchesGender = genderFilter === 'all' || player.gender === genderFilter;
if (matchesSearch && matchesGender) {
const card = document.createElement('div'); const card = document.createElement('div');
card.className = 'player-card'; card.className = 'player-card';
const strengthsHtml = player.strengths.map(s => `<span class="tag strength">${s}</span>`).join(''); 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(''); const weaknessesHtml = player.weaknesses.map(w => `<span class="tag weakness">${w}</span>`).join('');
const genderBadgeColor = player.gender === 'Messieurs' ? '#e85d04' : '#0077b6';
card.innerHTML = ` card.innerHTML = `
<div class="player-header"> <div class="player-header" style="background: linear-gradient(135deg, ${genderBadgeColor}, #faedcd)">
<img src="${player.photo_url}" alt="${player.name}" onerror="this.src='https://via.placeholder.com/120?text=${encodeURIComponent(player.name.charAt(0))}'"> <span style="position:absolute; top:10px; right:10px; background:rgba(255,255,255,0.2); padding: 2px 8px; border-radius:10px; color:white; font-size:0.75rem; font-weight:bold;">${player.gender}</span>
<img src="${player.photo}" alt="${player.name}">
<h3>${player.name}</h3> <h3>${player.name}</h3>
<p>#${player.ranking} Mondial</p> <p>#${player.ranking} Mondial${player.points} pts</p>
</div> </div>
<div class="player-body"> <div class="player-body">
<div class="stat-row"> <div class="stat-row"><span>🎂 Âge (en 2026)</span><strong>${player.age} ans</strong></div>
<span>Âge</span> <div class="stat-row"><span>📏 Taille</span><strong>${player.height}</strong></div>
<strong>${player.age} ans</strong> <div class="stat-row"><span>🌍 Nationalité</span><strong>${player.nationality}</strong></div>
</div> <div class="stat-row"><span>🤝 Latéralité</span><strong>${player.handedness}</strong></div>
<div class="stat-row"> <div class="stat-row"><span>📊 Efficacité Terre Battue</span><strong style="color:#e85d04">${(player.surfaceStats.clay.winRate * 100).toFixed(0)}%</strong></div>
<span>Nationalité</span> <div class="strengths"><h4>💪 Points forts</h4>${strengthsHtml}</div>
<strong>${player.nationality}</strong> <div class="weaknesses" style="margin-top:10px;"><h4>⚠️ Points faibles</h4>${weaknessesHtml}</div>
</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> </div>
`; `;
container.appendChild(card); container.appendChild(card);
});
} catch (error) {
alert('Erreur lors du chargement des joueurs: ' + error.message);
} }
});
} }
// Modal de pronostic avec analyse depuis la BDD // --- MODAL & ANALYSE TACTIQUE DYNAMIQUE ---
async function openPredictionModal(match) { function openPredictionModal(match) {
if (match.status === 'completed') return; if (match.status === 'completed') return;
currentMatch = match; currentMatch = match;
try { const p1 = playersData[match.player1];
// Récupérer l'analyse du matchup const p2 = playersData[match.player2];
const result = await apiCall(`player.php?action=matchup&player1=${match.player1.id}&player2=${match.player2.id}`);
const modal = document.getElementById('predictionModal'); const modal = document.getElementById('predictionModal');
const details = document.getElementById('matchDetails'); const details = document.getElementById('matchDetails');
details.innerHTML = ` details.innerHTML = `
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; margin-bottom: 2rem;"> <div style="display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; margin-bottom: 1.5rem; text-align: center;">
<div style="text-align: center;"> <div>
<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))}'"> <img src="${p1.photo}" style="width: 80px; height: 80px; border-radius: 50%; border: 3px solid #e85d04;">
<h3>${match.player1.name}</h3> <h3>${p1.name}</h3>
<p>${match.player1.nationality}</p> <p style="color:#666; font-size:0.9rem;">#${p1.ranking} Mondial</p>
</div> </div>
<div style="text-align: center;"> <div>
<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))}'"> <img src="${p2.photo}" style="width: 80px; height: 80px; border-radius: 50%; border: 3px solid #e85d04;">
<h3>${match.player2.name}</h3> <h3>${p2.name}</h3>
<p>${match.player2.nationality}</p> <p style="color:#666; font-size:0.9rem;">#${p2.ranking} Mondial</p>
</div> </div>
</div> </div>
<p style="text-align: center; color: #666;">${match.round} - ${match.court}</p> <p style="text-align: center; font-weight: 600; color: #e85d04;">${match.round} ${match.court}</p>
`; `;
// Afficher les probabilités // Calcul des probabilités de réussite via l'algorithme croisé
document.getElementById('prob1').style.width = `${result.probabilities.player1}%`; const probs = calculateWinProbability(match.player1, match.player2);
document.getElementById('prob2').style.width = `${result.probabilities.player2}%`; document.getElementById('prob1').style.width = `${probs.player1}%`;
document.getElementById('probText1').textContent = `${result.probabilities.player1}%`; document.getElementById('prob2').style.width = `${probs.player2}%`;
document.getElementById('probText2').textContent = `${result.probabilities.player2}%`; document.getElementById('probText1').textContent = `${probs.player1}%`;
document.getElementById('probText2').textContent = `${probs.player2}%`;
// Afficher l'analyse document.getElementById('predictBtn1').textContent = `Miser sur ${p1.name}`;
document.getElementById('predictBtn2').textContent = `Miser sur ${p2.name}`;
// Calcul dynamique des forces/défauts en face à face
const analysis = getMatchupAnalysis(match.player1, match.player2);
const analysisDiv = document.getElementById('matchupAnalysis'); const analysisDiv = document.getElementById('matchupAnalysis');
analysisDiv.innerHTML = ` analysisDiv.innerHTML = `
<h3>Analyse du Matchup</h3> <h3 style="margin-bottom:10px; font-size:1.1rem; border-bottom:1px solid #ddd; padding-bottom:5px;">🔬 Analyse tactique personnalisée</h3>
<div class="analysis-grid"> <div class="analysis-grid" style="display:grid; grid-template-columns: 1fr 1fr; gap:1.5rem;">
<div class="advantage"> <div class="advantage">
<h4>Avantages ${match.player1.name}</h4> <h4 style="color:#28a745;">👍 Avantages de ${p1.name}</h4>
<ul> <ul style="padding-left:15px; font-size:0.85rem; margin-top:5px;">
${result.analysis.player1_advantages.map(a => `<li>${a}</li>`).join('')} ${analysis.player1Advantages.map(a => `<li style="margin-bottom:4px;">${a}</li>`).join('')}
</ul>
<h4 style="color:#dc3545; margin-top:10px;">👎 Faiblesses exploitables</h4>
<ul style="padding-left:15px; font-size:0.85rem; margin-top:5px; color:#555;">
${analysis.player1Disadvantages.map(d => `<li style="margin-bottom:4px;">${d}</li>`).join('')}
</ul> </ul>
</div> </div>
<div class="advantage"> <div class="advantage">
<h4>Avantages ${match.player2.name}</h4> <h4 style="color:#28a745;">👍 Avantages de ${p2.name}</h4>
<ul> <ul style="padding-left:15px; font-size:0.85rem; margin-top:5px;">
${result.analysis.player2_advantages.map(a => `<li>${a}</li>`).join('')} ${analysis.player2Advantages.map(a => `<li style="margin-bottom:4px;">${a}</li>`).join('')}
</ul>
<h4 style="color:#dc3545; margin-top:10px;">👎 Faiblesses exploitables</h4>
<ul style="padding-left:15px; font-size:0.85rem; margin-top:5px; color:#555;">
${analysis.player2Disadvantages.map(d => `<li style="margin-bottom:4px;">${d}</li>`).join('')}
</ul> </ul>
</div> </div>
</div> </div>
`; `;
modal.style.display = 'block'; modal.style.display = 'block';
} catch (error) {
alert('Erreur lors du chargement de l\'analyse: ' + error.message);
}
} }
function closePredictionModal() { function closePredictionModal() {
@@ -240,79 +219,47 @@ function closePredictionModal() {
currentMatch = null; currentMatch = null;
} }
// Faire un pronostic via l'API function makePrediction(playerNum) {
async function makePrediction(playerNum) { if (!currentMatch) return;
if (!currentMatch || !currentUser) return; const selectedPlayer = playerNum === 1 ? playersData[currentMatch.player1] : playersData[currentMatch.player2];
const winnerId = playerNum === 1 ? currentMatch.player1.id : currentMatch.player2.id; currentUser.points += 10; // Gain de participation standard
const winnerName = playerNum === 1 ? currentMatch.player1.name : currentMatch.player2.name; currentUser.predictionsCount += 1;
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)); localStorage.setItem('currentUser', JSON.stringify(currentUser));
initDashboard();
alert(`Pronostic enregistré! Vous avez choisi ${winnerName}\n+${result.points_earned} points`); alert(`🎯 Pronostic enregistré avec succès pour ${selectedPlayer.name} ! +10 points ajoutés.`);
closePredictionModal(); closePredictionModal();
await loadStats();
await loadLeaderboard();
} catch (error) {
alert(error.message);
}
} }
// Charger le classement depuis la BDD // --- CLASSEMENT ET STATISTIQUES ---
async function loadLeaderboard() { function loadLeaderboard() {
try {
const result = await apiCall('predictions.php?action=leaderboard');
const tbody = document.getElementById('leaderboardBody'); const tbody = document.getElementById('leaderboardBody');
tbody.innerHTML = ''; if(!tbody) return;
tbody.innerHTML = `
result.leaderboard.forEach(user => { <tr style="font-weight:bold; background:#faf3e0;">
const row = document.createElement('tr'); <td>🥇 1</td><td>${currentUser.username} (Vous)</td><td>${currentUser.points}</td><td>${Math.round(currentUser.predictionsCount * (currentUser.successRate/100))}</td>
const isCurrentUser = user.user_id === currentUser.id; </tr>
<tr><td>🥈 2</td><td>BabolatFan_26</td><td>290</td><td>5</td></tr>
row.innerHTML = ` <tr><td>🥉 3</td><td>ClayCourtKing</td><td>240</td><td>4</td></tr>
<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 function loadStats() {
async function loadStats() { document.getElementById('totalPredictions').textContent = currentUser.predictionsCount;
try { document.getElementById('successRate').textContent = `${currentUser.successRate}%`;
const result = await apiCall('predictions.php?action=stats'); document.getElementById('bestStreak').textContent = "3";
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 function logout() {
localStorage.clear();
alert("Session clôturée.");
location.reload();
}
// Fermeture du modal au clic à l'extérieur
window.onclick = function(event) { window.onclick = function(event) {
const modal = document.getElementById('predictionModal'); const modal = document.getElementById('predictionModal');
if (event.target === modal) { if (event.target === modal) closePredictionModal();
closePredictionModal(); };
}
}