Files
Sdk_TV_app/templates/modern_player_analysis.html
T

319 lines
13 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title data-i18n="players.player_analysis">👤 Player Analysis - Camera Dashboard</title>
<link rel="stylesheet" href="/static/css/base.css">
<link rel="stylesheet" href="/static/css/navbar.css">
<link rel="stylesheet" href="/static/css/buttons.css">
<link rel="stylesheet" href="/static/css/components.css">
<link rel="stylesheet" href="/static/css/responsive.css">
<link rel="stylesheet" href="/static/css/pages/modern_player_analysis.css">
<script src="/static/js/chart.min.js"></script>
<script>
// Fallback: if Chart.js fails to load, create a simple notification
if (typeof Chart === 'undefined') {
console.warn('Chart.js failed to load - charts will be unavailable');
window.Chart = {
register: () => {},
Chart: function() {
return {
update: () => {},
destroy: () => {},
data: {},
options: {}
};
}
};
}
</script>
<script src="/static/js/i18n.js"></script>
</head>
<body>
<!-- Navigation Bar -->
<div class="navbar">
<div class="navbar-title">👤 Analiza Igralcev</div>
<div class="navbar-controls">
<a href="/archive/player-analysis" class="nav-btn active">👤 <span data-i18n="players.player_analysis">Analiza Igralcev</span></a>
<a href="/archive" class="nav-btn">📚 <span data-i18n="navigation.archive">Archive</span></a>
<a href="/" class="nav-btn">✕</a>
</div>
</div>
<div class="players-layout">
<!-- LEFT: player tree -->
<div class="players-left-panel">
<div class="players-panel-header">
<div class="players-panel-header-title">👥 Igralci</div>
<div class="players-panel-count" id="playerTreeCount">0</div>
</div>
<div class="players-sort-bar">
<input type="text" class="players-search" id="searchBox" placeholder="🔍 Iskanje…" oninput="filterAndSortPlayers()">
<div class="players-sort-btns">
<button class="psort-btn active" data-sort="best_score" onclick="setSort('best_score')">🏆 Skupaj</button>
<button class="psort-btn" data-sort="total_tens" onclick="setSort('total_tens')">🎯 Desetke</button>
<button class="psort-btn" data-sort="best_single" onclick="setSort('best_single')">⭐ Enkrat</button>
</div>
</div>
<div class="player-tree-list" id="playerTreeList">
<div class="loading"><div class="loading-spinner"></div></div>
</div>
</div>
<!-- RIGHT: player detail -->
<div class="players-right-panel" id="playersRightPanel">
<div class="player-placeholder" id="playerPlaceholder">
<div class="player-placeholder-icon">👤</div>
<div class="player-placeholder-text">Izberi igralca iz seznama</div>
</div>
<div id="playerDetailEmbed" style="display:none"></div>
</div>
</div>
<script>
// Global data
let playersData = [];
let tournamentData = [];
let filteredPlayers = [];
let currentFilter = 'all';
let currentSort = 'best_score';
let playersWithStats = [];
// Initialize page
function initializePage() {
loadPlayerStats();
setupEventListeners();
}
// Format date for display
function formatDate(dateString) {
if (!dateString) return t('analysis.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 t('analysis.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 {
const response = await fetch('/api/archive/players/with-stats');
const result = await response.json();
if (result.status === 'success') {
playersWithStats = result.players;
playersData = playersWithStats;
filteredPlayers = [...playersWithStats];
// Update total players count
document.getElementById('playerTreeCount').textContent = playersData.length;
filterAndSortPlayers();
} else {
console.error('Failed to load player stats:', result.message);
renderPlayersBasic();
}
} catch (error) {
console.error('Error loading player stats:', error);
renderPlayersBasic();
}
}
// Setup event listeners
function setupEventListeners() {
// search handled inline via oninput on the input element
}
function setSort(sort) {
currentSort = sort;
document.querySelectorAll('.psort-btn').forEach(b =>
b.classList.toggle('active', b.dataset.sort === sort));
filterAndSortPlayers();
}
// Filter and sort players
function filterAndSortPlayers() {
const searchTerm = document.getElementById('searchBox').value.toLowerCase();
// Sort full list and assign permanent ranks
const sorted = [...playersData].sort((a, b) => {
switch (currentSort) {
case 'best_score': return (b.stats?.best_tournament_score || 0) - (a.stats?.best_tournament_score || 0);
case 'total_tens': return (b.stats?.total_tens || 0) - (a.stats?.total_tens || 0);
case 'best_single': return (b.stats?.best_tournament_score || 0) - (a.stats?.best_tournament_score || 0);
default: return 0;
}
});
sorted.forEach((p, i) => { p._rank = i + 1; });
// Filter preserving original rank
filteredPlayers = sorted.filter(player => {
const matchesSearch = player.name.toLowerCase().includes(searchTerm);
const matchesFilter = currentFilter === 'all' ||
(currentFilter === 'current' && player.current_player) ||
(currentFilter === 'archived' && !player.current_player);
return matchesSearch && matchesFilter;
});
renderPlayers();
}
// Render players into the tree panel
let activePlayerId = null;
function renderPlayers() {
const treeList = document.getElementById('playerTreeList');
document.getElementById('playerTreeCount').textContent = filteredPlayers.length;
if (filteredPlayers.length === 0) {
treeList.innerHTML = `<div style="padding:24px;text-align:center;color:#bbb;font-size:0.82rem">Ni zadetkov</div>`;
return;
}
treeList.innerHTML = filteredPlayers.map((player) => {
const initials = player.name.split(' ').map(w => w[0]).join('').slice(0, 2).toUpperCase();
const active = player.id === activePlayerId ? ' active' : '';
let scoreVal, scoreMeta;
if (currentSort === 'total_tens') {
scoreVal = player.stats?.total_tens || 0;
scoreMeta = `${scoreVal} 🎯`;
} else {
scoreVal = player.stats?.best_tournament_score || 0;
scoreMeta = `${scoreVal} pts`;
}
const rank = player._rank || 0;
const rankHtml = rank === 1 ? `<div class="player-tree-rank">🥇</div>`
: rank === 2 ? `<div class="player-tree-rank">🥈</div>`
: rank === 3 ? `<div class="player-tree-rank">🥉</div>`
: `<div class="player-tree-rank-num">${rank}</div>`;
return `<div class="player-tree-item${active}" onclick="selectPlayer(${player.id})">
${rankHtml}
<div class="player-tree-info">
<div class="player-tree-name">${player.name}</div>
<div class="player-tree-meta">${player.stats?.total_tournaments || 0}T · ${scoreMeta}</div>
</div>
</div>`;
}).join('');
}
// Render players without stats (fallback)
function renderPlayersBasic() {
fetch('/api/players')
.then(response => response.json())
.then(data => {
const basicPlayers = data.players || [];
playersData = basicPlayers.map(p => ({ id: p.id, name: p.name, current_player: p.enabled, stats: {} }));
filteredPlayers = [...playersData];
document.getElementById('playerTreeCount').textContent = playersData.length;
renderPlayers();
})
.catch(() => {
document.getElementById('playerTreeList').innerHTML =
`<div style="padding:24px;text-align:center;color:#bbb;font-size:0.82rem">Napaka pri nalaganju</div>`;
});
}
// Create mini performance chart for player
function createMiniChart(player) {
const canvas = document.getElementById(`chart-${player.id}`);
if (!canvas || !player.stats?.performance_trend) return;
const ctx = canvas.getContext('2d');
new Chart(ctx, {
type: 'line',
data: {
labels: player.stats.performance_trend.map((_, i) => `T${i + 1}`),
datasets: [{
data: player.stats.performance_trend,
borderColor: player.current_player ? '#28a745' : '#6c757d',
backgroundColor: player.current_player ? 'rgba(40, 167, 69, 0.1)' : 'rgba(108, 117, 125, 0.1)',
borderWidth: 2,
fill: true,
tension: 0.4,
pointRadius: 2,
pointHoverRadius: 4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
display: false
}
},
scales: {
x: {
display: false
},
y: {
display: false,
beginAtZero: true
}
},
elements: {
point: {
hoverBackgroundColor: player.current_player ? '#28a745' : '#6c757d'
}
},
interaction: {
intersect: false,
mode: 'index'
}
}
});
}
// Select player — load stats into right panel iframe
function selectPlayer(playerId) {
activePlayerId = playerId;
renderPlayers();
document.getElementById('playerPlaceholder').style.display = 'none';
const embed = document.getElementById('playerDetailEmbed');
embed.style.display = 'block';
embed.innerHTML = `<iframe src="/archive/player/${playerId}"></iframe>`;
}
// Navigate to player stats (kept for compatibility)
function viewPlayerStats(playerId) {
selectPlayer(playerId);
}
// Initialize page when DOM is loaded and translations are ready
document.addEventListener('DOMContentLoaded', function() {
window.addEventListener('i18nReady', function() {
initializePage();
}, { once: true });
});
</script>
</body>
</html>