Print function correction + player analysis fix and player stats fix

This commit is contained in:
bl3kunja-FW
2025-08-10 18:22:22 +02:00
parent 054c81e78e
commit 33758e7340
7 changed files with 1678 additions and 383 deletions
+450 -97
View File
@@ -65,28 +65,6 @@
color: #007bff;
}
.nav-btn {
background: #f8f9fa;
border: 2px solid #e9ecef;
cursor: pointer;
padding: 12px 20px;
border-radius: 8px;
transition: all 0.2s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
color: #333;
text-decoration: none;
font-weight: bold;
font-size: 0.9rem;
}
.nav-btn:hover {
background: #e9ecef;
border-color: #007bff;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
transform: translateY(-1px);
color: #007bff;
}
.nav-btn.secondary {
background: #6c757d;
border-color: #495057;
@@ -146,10 +124,142 @@
gap: 10px;
}
/* Tab Navigation */
.tab-navigation {
display: flex;
gap: 5px;
margin-bottom: 25px;
border-bottom: 2px solid #f1f3f4;
}
.tab-btn {
background: none;
border: none;
padding: 12px 24px;
cursor: pointer;
font-size: 1rem;
font-weight: 600;
color: #666;
border-bottom: 3px solid transparent;
transition: all 0.3s ease;
}
.tab-btn.active {
color: #007bff;
border-bottom-color: #007bff;
}
.tab-btn:hover {
color: #007bff;
background: rgba(0, 123, 255, 0.05);
}
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
/* Tournament Leaders Section */
.tournament-leaders {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 20px;
margin-bottom: 30px;
}
.tournament-card {
background: white;
border-radius: 12px;
padding: 20px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
border: 1px solid #e9ecef;
position: relative;
overflow: hidden;
}
.tournament-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, #ffc107, #fd7e14);
}
.tournament-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.tournament-name {
font-size: 1.2rem;
font-weight: bold;
color: #333;
}
.tournament-date {
background: #f8f9fa;
color: #666;
padding: 4px 10px;
border-radius: 12px;
font-size: 0.8rem;
font-weight: 500;
}
.leaders-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
}
.leader-category {
background: #f8f9fa;
border-radius: 8px;
padding: 15px;
text-align: center;
}
.category-title {
font-size: 0.85rem;
font-weight: 600;
color: #666;
margin-bottom: 8px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.leader-info {
display: flex;
flex-direction: column;
gap: 4px;
}
.leader-name {
font-size: 1.1rem;
font-weight: bold;
color: #333;
}
.leader-score {
font-size: 1.3rem;
font-weight: bold;
color: #007bff;
}
.leader-score.tens {
color: #28a745;
}
/* Compact Stats Overview */
.stats-overview {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 15px;
margin-bottom: 20px;
}
@@ -453,7 +563,25 @@
}
.stats-overview {
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(110px, 1fr));
}
.tournament-leaders {
grid-template-columns: 1fr;
}
.leaders-grid {
grid-template-columns: 1fr;
}
.tab-navigation {
flex-wrap: wrap;
gap: 2px;
}
.tab-btn {
padding: 8px 16px;
font-size: 0.9rem;
}
}
</style>
@@ -461,7 +589,7 @@
<body>
<!-- Navigation Bar -->
<div class="navbar">
<div class="navbar-title">👤 Player Analysis</div>
<div class="navbar-title">🎯 Player Analysis</div>
<div class="navbar-controls">
<a href="/" class="nav-btn primary">← Dashboard</a>
<a href="/archive" class="nav-btn secondary">📚 Archive</a>
@@ -473,52 +601,84 @@
<div class="stats-overview">
<div class="stat-card">
<span class="stat-icon">👥</span>
<div class="stat-value" id="totalPlayers">{{ players|length }}</div>
<div class="stat-value" id="totalPlayers">Loading...</div>
<div class="stat-label">Total Players</div>
</div>
<div class="stat-card">
<span class="stat-icon">📊</span>
<div class="stat-value" id="avgTournaments">{{ overview_stats.avg_tournaments if overview_stats else 0 }}</div>
<div class="stat-label">Avg Tournaments</div>
<span class="stat-icon">🎯</span>
<div class="stat-value" id="totalTournaments">Loading...</div>
<div class="stat-label">Total Tournaments</div>
</div>
<div class="stat-card">
<span class="stat-icon">🏆</span>
<div class="stat-value" id="topScore">{{ overview_stats.top_score if overview_stats else 0 }}</div>
<div class="stat-label">Highest Score</div>
<div class="stat-value" id="score20Targets">Loading...</div>
<div class="stat-label">Best 20 Targets</div>
</div>
<div class="stat-card">
<span class="stat-icon">🎖️</span>
<div class="stat-value" id="score40Targets">Loading...</div>
<div class="stat-label">Best 40 Targets</div>
</div>
<div class="stat-card">
<span class="stat-icon">🥇</span>
<div class="stat-value" id="score4Targets">Loading...</div>
<div class="stat-label">Best 4 Targets</div>
</div>
</div>
<!-- Tab Navigation -->
<div class="section">
<div class="section-title">Select a Player to Analyze</div>
<!-- Controls -->
<div class="controls">
<input type="text" class="search-box" id="searchBox" placeholder="🔍 Search players by name...">
<select class="sort-select" id="sortSelect">
<option value="name">Sort by Name</option>
<option value="best_score">Sort by Best Score</option>
<option value="average_score">Sort by Average Score</option>
<option value="total_tournaments">Sort by Total Tournaments</option>
<option value="total_leagues">Sort by Total Leagues</option>
<option value="total_shots">Sort by Total Shots</option>
</select>
<div class="tab-navigation">
<button class="tab-btn active" onclick="switchTab('tournament-leaders')">🏆 Overall Champions</button>
<button class="tab-btn" onclick="switchTab('players')">👥 All Players</button>
</div>
<!-- Players Grid -->
<div class="players-grid" id="playersGrid">
<div class="loading">
<div class="loading-spinner"></div>
<p>Loading players...</p>
<!-- Tournament Leaders Tab -->
<div id="tournament-leaders" class="tab-content active">
<div class="section-title">Overall Champions by Tournament Type</div>
<div class="tournament-leaders" id="tournamentLeaders">
<div class="loading">
<div class="loading-spinner"></div>
<p>Loading tournament data...</p>
</div>
</div>
</div>
<!-- Players Tab -->
<div id="players" class="tab-content">
<div class="section-title">Select a Player to Analyze</div>
<!-- Controls -->
<div class="controls">
<input type="text" class="search-box" id="searchBox" placeholder="🔍 Search players by name...">
<select class="sort-select" id="sortSelect">
<option value="name">Sort by Name</option>
<option value="best_score">Sort by Best Score</option>
<option value="average_score">Sort by Average Score</option>
<option value="total_tournaments">Sort by Total Tournaments</option>
<option value="total_leagues">Sort by Total Leagues</option>
<option value="total_shots">Sort by Total Shots</option>
</select>
</div>
<!-- Players Grid -->
<div class="players-grid" id="playersGrid">
<div class="loading">
<div class="loading-spinner"></div>
<p>Loading players...</p>
</div>
</div>
</div>
</div>
</div>
<script>
// Players data from Flask
let playersData = {{ players|tojson }};
let filteredPlayers = [...playersData];
// Global data
let playersData = [];
let tournamentData = [];
let filteredPlayers = [];
let currentFilter = 'all';
let currentSort = 'name';
let playersWithStats = [];
@@ -526,9 +686,180 @@
// Initialize page
function initializePage() {
loadPlayerStats();
loadTournamentLeaders();
setupEventListeners();
}
// Switch between tabs
function switchTab(tabName) {
// Hide all tab contents
document.querySelectorAll('.tab-content').forEach(content => {
content.classList.remove('active');
});
// Remove active class from all tab buttons
document.querySelectorAll('.tab-btn').forEach(btn => {
btn.classList.remove('active');
});
// Show selected tab content
document.getElementById(tabName).classList.add('active');
// Add active class to clicked button
event.target.classList.add('active');
}
// Load tournament leaders data
async function loadTournamentLeaders() {
try {
const response = await fetch('/api/archive/tournament-leaders');
const result = await response.json();
if (result.status === 'success') {
tournamentData = result.tournament_types;
renderTournamentLeaders();
updateOverallStats();
} else {
console.error('Failed to load tournament data:', result.message);
document.getElementById('tournamentLeaders').innerHTML = `
<div class="empty-state">
<div class="empty-icon">🎯</div>
<h3>Unable to Load Tournament Data</h3>
<p>${result.message || 'Please try refreshing the page'}</p>
</div>
`;
}
} catch (error) {
console.error('Error loading tournament data:', error);
document.getElementById('tournamentLeaders').innerHTML = `
<div class="empty-state">
<div class="empty-icon">🎯</div>
<h3>Unable to Load Tournament Data</h3>
<p>Please try refreshing the page</p>
</div>
`;
}
}
// Render tournament leaders
function renderTournamentLeaders() {
const leadersContainer = document.getElementById('tournamentLeaders');
if (tournamentData.length === 0) {
leadersContainer.innerHTML = `
<div class="empty-state">
<div class="empty-icon">🎯</div>
<h3>No Tournament Data Available</h3>
<p>Tournament results will appear here once available</p>
</div>
`;
return;
}
leadersContainer.innerHTML = tournamentData.map(tournamentType => `
<div class="tournament-card">
<div class="tournament-header">
<div class="tournament-name">${tournamentType.name}</div>
<div class="tournament-date">${tournamentType.total_tournaments} tournament${tournamentType.total_tournaments !== 1 ? 's' : ''}</div>
</div>
<div style="text-align: center; margin-bottom: 15px; color: #666; font-size: 0.9rem; font-weight: 500;">
${tournamentType.description}
</div>
<div class="leaders-grid">
<div class="leader-category">
<div class="category-title">🏆 Best Score</div>
<div class="leader-info">
<div class="leader-name">${tournamentType.best_score.player_name || 'No data'}</div>
<div class="leader-score">${tournamentType.best_score.score || 0}</div>
</div>
</div>
<div class="leader-category">
<div class="category-title">🎪 Most 10s</div>
<div class="leader-info">
<div class="leader-name">${tournamentType.most_tens.player_name || 'No data'}</div>
<div class="leader-score tens">${tournamentType.most_tens.tens || 0} 10s</div>
</div>
</div>
</div>
</div>
`).join('');
}
// Update overall statistics
function updateOverallStats() {
let totalTournaments = 0;
let score20Targets = 0;
let score40Targets = 0;
let score4Targets = 0;
// Calculate statistics across all tournament types
tournamentData.forEach(tournamentType => {
totalTournaments += tournamentType.total_tournaments || 0;
// Get best scores for each tournament type
if (tournamentType.id === '20_targets' && tournamentType.best_score) {
score20Targets = tournamentType.best_score.score || 0;
} else if (tournamentType.id === '40_targets' && tournamentType.best_score) {
score40Targets = tournamentType.best_score.score || 0;
} else if (tournamentType.id === '4_targets' && tournamentType.best_score) {
score4Targets = tournamentType.best_score.score || 0;
}
});
document.getElementById('totalTournaments').textContent = totalTournaments;
document.getElementById('score20Targets').textContent = score20Targets || '-';
document.getElementById('score40Targets').textContent = score40Targets || '-';
document.getElementById('score4Targets').textContent = score4Targets || '-';
}
// Get tournament type display name
function getTournamentTypeDisplay(tournamentType) {
const typeMap = {
'20_targets': '20 Targets (2 shots each)',
'40_targets': '40 Targets (2 shots each)',
'4_targets': '4 Targets (5 shots each)'
};
return typeMap[tournamentType] || tournamentType;
}
// Format date for display
function formatDate(dateString) {
if (!dateString) return 'Unknown Date';
try {
const date = new Date(dateString);
if (isNaN(date.getTime())) {
// Try to parse filename format YYYYMMDD_HHMMSS
if (dateString.length >= 8) {
const year = dateString.substring(0, 4);
const month = dateString.substring(4, 6);
const day = dateString.substring(6, 8);
const parsedDate = new Date(`${year}-${month}-${day}`);
if (!isNaN(parsedDate.getTime())) {
return parsedDate.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
});
}
}
return 'Unknown Date';
}
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric'
});
} catch (error) {
return 'Unknown Date';
}
}
// Load player statistics
async function loadPlayerStats() {
try {
@@ -539,9 +870,13 @@
playersWithStats = result.players;
playersData = playersWithStats;
filteredPlayers = [...playersWithStats];
// Update total players count
document.getElementById('totalPlayers').textContent = playersData.length;
filterAndSortPlayers();
} else {
console.error('Failed to load player stats');
console.error('Failed to load player stats:', result.message);
renderPlayersBasic();
}
} catch (error) {
@@ -554,22 +889,12 @@
function setupEventListeners() {
const searchBox = document.getElementById('searchBox');
const sortSelect = document.getElementById('sortSelect');
const filterButtons = document.querySelectorAll('.filter-btn');
searchBox.addEventListener('input', filterAndSortPlayers);
sortSelect.addEventListener('change', (e) => {
currentSort = e.target.value;
filterAndSortPlayers();
});
filterButtons.forEach(btn => {
btn.addEventListener('click', () => {
filterButtons.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
currentFilter = btn.dataset.filter;
filterAndSortPlayers();
});
});
}
// Filter and sort players
@@ -675,42 +1000,70 @@
// Render players without stats (fallback)
function renderPlayersBasic() {
const playersGrid = document.getElementById('playersGrid');
playersGrid.innerHTML = playersData.map(player => `
<div class="player-card ${player.current_player ? 'current-player' : 'archived-player'}"
onclick="viewPlayerStats(${player.id})">
<div class="player-header">
<div class="player-name">${player.name}</div>
<div class="player-badge ${player.current_player ? 'current' : 'archived'}">
${player.current_player ? 'Current' : 'Archived'}
</div>
</div>
// If we can't get stats, try to get basic player list
fetch('/api/players')
.then(response => response.json())
.then(data => {
const basicPlayers = data.players || [];
const playersGrid = document.getElementById('playersGrid');
<div class="player-stats">
<div class="stat-item">
<span class="icon">🎯</span>
<span class="label">Tournaments:</span>
<span class="value">Loading...</span>
if (basicPlayers.length === 0) {
playersGrid.innerHTML = `
<div class="empty-state">
<div class="empty-icon">👤</div>
<h3>No Players Found</h3>
<p>No player data available</p>
</div>
`;
return;
}
playersGrid.innerHTML = basicPlayers.map(player => `
<div class="player-card ${player.enabled ? 'current-player' : 'archived-player'}"
onclick="viewPlayerStats(${player.id})">
<div class="player-header">
<div class="player-name">${player.name}</div>
<div class="player-badge ${player.enabled ? 'current' : 'archived'}">
${player.enabled ? 'Current' : 'Archived'}
</div>
</div>
<div class="player-stats">
<div class="stat-item">
<span class="icon">🎯</span>
<span class="label">Tournaments:</span>
<span class="value">Loading...</span>
</div>
<div class="stat-item">
<span class="icon">🏆</span>
<span class="label">Leagues:</span>
<span class="value">Loading...</span>
</div>
<div class="stat-item">
<span class="icon">📈</span>
<span class="label">Best Score:</span>
<span class="value">Loading...</span>
</div>
<div class="stat-item">
<span class="icon">📊</span>
<span class="label">Average:</span>
<span class="value">Loading...</span>
</div>
</div>
</div>
<div class="stat-item">
<span class="icon">🏆</span>
<span class="label">Leagues:</span>
<span class="value">Loading...</span>
`).join('');
})
.catch(error => {
console.error('Error loading basic players:', error);
const playersGrid = document.getElementById('playersGrid');
playersGrid.innerHTML = `
<div class="empty-state">
<div class="empty-icon">❌</div>
<h3>Error Loading Players</h3>
<p>Unable to load player data</p>
</div>
<div class="stat-item">
<span class="icon">📈</span>
<span class="label">Best Score:</span>
<span class="value">Loading...</span>
</div>
<div class="stat-item">
<span class="icon">📊</span>
<span class="label">Average:</span>
<span class="value">Loading...</span>
</div>
</div>
</div>
`).join('');
`;
});
}
// Create mini performance chart for player