Files
Sdk_TV_app/templates/modern_player_stats.html
T

2502 lines
89 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{ player.name }} - Player Stats</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">
<script src="/static/js/chart.min.js"></script>
<style>
/* Player stats specific styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: Arial, sans-serif;
background: #f5f5f5;
min-height: 100vh;
color: #333;
}
body.in-iframe .navbar { display: none; }
body.in-iframe { background: transparent; }
/* Standardized Container */
.container {
max-width: 1400px;
margin: 0 auto;
padding: 20px;
}
/* Stats Overview */
.stats-badges {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 15px;
margin-bottom: 20px;
}
.stat-badge {
background: white;
border-radius: 12px;
padding: 15px;
text-align: center;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
border: 1px solid #e9ecef;
transition: transform 0.3s ease;
}
.stat-badge:hover {
transform: translateY(-2px);
}
.stat-icon {
font-size: 1.5rem;
margin-bottom: 8px;
display: block;
}
.stat-number {
font-size: 1.5rem;
font-weight: bold;
color: #333;
margin-bottom: 5px;
}
.stat-label {
color: #666;
font-size: 0.75rem;
font-weight: 500;
}
/* Tournament Badge Styling */
.stat-badge.tournament-badge {
display: flex;
flex-direction: column;
justify-content: center;
}
.tournament-scores {
display: flex;
gap: 10px;
margin-bottom: 8px;
justify-content: center;
}
.tournament-score-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
}
.tournament-type {
font-size: 0.65rem;
color: #999;
font-weight: 600;
text-transform: uppercase;
}
.tournament-emoji {
font-size: 1rem;
margin-bottom: 2px;
}
.tournament-best {
font-size: 1.3rem;
font-weight: bold;
color: #28a745;
}
/* Charts Section */
.charts-section {
min-height: 450px;
flex-shrink: 0;
margin-top: 30px;
}
/* Stats Panel */
.stats-panel {
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 18px;
display: flex;
flex-direction: column;
}
.panel-title {
font-size: 1.1rem;
font-weight: bold;
color: #333;
margin-bottom: 15px;
border-bottom: 2px solid #28a745;
padding-bottom: 5px;
}
.stats-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
flex: 1;
}
.stat-item {
text-align: center;
padding: 12px 8px;
background: #f8f9fa;
border-radius: 8px;
display: flex;
flex-direction: column;
justify-content: center;
}
.stat-value {
font-size: 1.4rem;
font-weight: bold;
color: #28a745;
margin-bottom: 3px;
}
.stat-label {
font-size: 0.7rem;
color: #666;
text-transform: uppercase;
font-weight: 500;
}
/* Charts Panel */
.charts-panel {
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 18px;
display: flex;
flex-direction: column;
width: 100%;
}
/* Tournament Type Buttons */
.tournament-type-buttons {
display: flex;
gap: 10px;
margin-bottom: 10px;
flex-wrap: wrap;
width: auto;
}
.type-btn {
background: #f8f9fa;
border: 2px solid #e9ecef;
padding: 12px 20px;
border-radius: 8px;
cursor: pointer;
transition: all 0.3s ease;
font-size: 1rem;
font-weight: 600;
color: #333;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
flex: 1;
}
.type-btn:hover {
border-color: #28a745;
transform: translateY(-1px);
}
.type-btn.active {
color: white;
transform: translateY(-1px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
.type-btn.active.targets-40 {
background: #9C27B0;
border-color: #9C27B0;
}
.type-btn.active.targets-20 {
background: #FF9800;
border-color: #FF9800;
}
.type-btn.active.targets-4 {
background: #4CAF50;
border-color: #4CAF50;
}
.type-btn.targets-40:hover {
border-color: #9C27B0;
}
.type-btn.targets-20:hover {
border-color: #FF9800;
}
.type-btn.targets-4:hover {
border-color: #4CAF50;
}
/* Chart Info Header */
.chart-info {
margin-bottom: 15px;
}
.chart-info h3 {
margin: 0 0 15px 0;
color: #333;
font-size: 1.1rem;
font-weight: 600;
}
.chart-stats {
display: flex;
justify-content: space-around;
align-items: center;
gap: 20px;
font-size: 0.85rem;
padding: 12px;
background: transparent;
border-radius: 0;
border: none;
}
.chart-stat {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
}
.chart-stat-value {
font-size: 1.1rem;
font-weight: bold;
color: #333;
}
.chart-stat-label {
color: #666;
font-size: 0.75rem;
text-transform: uppercase;
}
/* Shot Accuracy Stats */
.accuracy-stats {
display: flex;
flex-wrap: wrap;
gap: 8px;
font-size: 0.8rem;
justify-content: center;
}
.accuracy-stat {
display: flex;
flex-direction: column;
align-items: center;
padding: 4px 8px;
background: white;
border-radius: 6px;
border: 1px solid #e9ecef;
min-width: 35px;
transition: transform 0.2s ease;
}
.accuracy-stat:hover {
transform: scale(1.05);
box-shadow: 0 2px 6px rgba(0,0,0,0.1);
}
.accuracy-value {
font-weight: bold;
color: #333;
font-size: 0.9rem;
}
.accuracy-label {
color: #666;
font-size: 0.65rem;
font-weight: 500;
}
.accuracy-charts-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
align-items: stretch;
}
.accuracy-chart-container {
position: relative;
min-height: 400px;
max-height: 500px;
background: transparent;
border-radius: 0;
border: none;
padding: 20px 0;
box-shadow: none;
width: 100%;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
}
.accuracy-chart-container canvas {
max-width: 100% !important;
max-height: 100% !important;
width: 100% !important;
height: auto !important;
}
/* Smaller radar chart */
.accuracy-charts-container > div:nth-child(2) .accuracy-chart-container {
min-height: 350px;
max-height: 420px;
}
.chart-container {
position: relative;
flex: 1;
min-height: 400px;
max-height: 500px;
background: transparent;
border-radius: 0;
border: none;
padding: 15px 0;
width: 100%;
box-sizing: border-box;
display: flex;
align-items: center;
justify-content: center;
box-shadow: none;
}
.chart-container canvas {
max-width: calc(100% - 10px) !important;
max-height: calc(100% - 10px) !important;
width: 100% !important;
height: auto !important;
}
.no-data {
display: flex;
align-items: center;
justify-content: center;
height: 200px;
color: #999;
font-size: 0.9rem;
font-style: italic;
background: #f8f9fa;
border-radius: 6px;
border: 1px dashed #dee2e6;
}
/* Accuracy and Performance Sections */
.accuracy-section {
background: transparent;
border-radius: 0;
padding: 0;
margin-bottom: 0;
border: none;
width: 100%;
}
.accuracy-section h3 {
margin: 0 0 15px 0;
color: #333;
font-size: 1.1rem;
font-weight: 600;
}
.performance-section {
background: #f8f9fa;
border-radius: 12px;
padding: 20px;
border: 1px solid #e9ecef;
}
.performance-section h3 {
margin: 0 0 15px 0;
color: #333;
font-size: 1.1rem;
font-weight: 600;
}
/* Bottom Section - History */
.bottom-section {
min-height: auto;
flex-shrink: 0;
margin-top: 30px;
}
.history-section {
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 18px;
display: flex;
flex-direction: column;
min-height: 400px;
border: 1px solid #e9ecef;
margin-top: 30px;
}
/* History Tabs */
.history-tabs {
display: flex;
gap: 10px;
margin: 20px 0;
}
.history-tab {
background: #f8f9fa;
border: 2px solid #e9ecef;
padding: 10px 16px;
cursor: pointer;
font-size: 0.95rem;
font-weight: 600;
color: #666;
border-radius: 8px;
transition: all 0.3s ease;
display: flex;
align-items: center;
gap: 6px;
}
.history-tab:hover {
background: #e9ecef;
color: #333;
border-color: #dee2e6;
}
.history-tab.active {
background: #28a745;
border-color: #28a745;
color: white;
}
/* Tab Content */
.history-tab-content {
display: none;
flex: 1;
overflow-y: auto;
}
.history-tab-content.active {
display: flex;
flex-direction: column;
}
.table-wrapper {
flex: 1;
overflow-y: auto;
border-radius: 8px;
border: 1px solid #e9ecef;
}
/* History Table */
.history-table {
width: 100%;
border-collapse: collapse;
font-size: 0.9rem;
}
.history-table thead {
background: #f8f9fa;
position: sticky;
top: 0;
}
.history-table th {
padding: 12px 16px;
text-align: left;
font-weight: 600;
color: #333;
border-bottom: 2px solid #dee2e6;
}
.history-table tbody tr {
border-bottom: 1px solid #e9ecef;
transition: background 0.2s ease;
}
.history-table td {
padding: 12px 16px;
color: #555;
}
.history-table-row {
cursor: pointer;
}
.history-table-row:hover {
background: #f8f9fa;
}
.tournament-type-badge {
display: inline-block;
padding: 4px 8px;
border-radius: 6px;
font-size: 0.8rem;
font-weight: 600;
background: #e3f2fd;
color: #1976d2;
}
.date-cell {
color: #666;
font-size: 0.9rem;
white-space: nowrap;
}
.score-cell {
color: #333;
font-weight: 600;
}
.view-link {
color: #28a745;
font-weight: 600;
transition: color 0.2s ease;
}
.history-table-row:hover .view-link {
color: #1e7e34;
}
.joker-badge {
margin-left: 4px;
}
/* Placer Statistics Grid */
.placer-stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: 12px;
margin-top: 20px;
}
.placer-stat-card {
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
border: 1px solid #e9ecef;
border-radius: 10px;
padding: 16px;
text-align: center;
transition: all 0.3s ease;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
cursor: default;
}
.placer-stat-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
background: linear-gradient(135deg, #ffffff 0%, #f0f4f8 100%);
}
.placer-stat-value {
font-size: 1.8rem;
font-weight: 700;
color: #28a745;
margin-bottom: 4px;
}
.placer-stat-label {
font-size: 0.75rem;
color: #666;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 8px;
}
.placer-stat-count {
font-size: 1.4rem;
font-weight: 700;
color: #333;
}
/* Overall Accuracy Card */
.overall-accuracy-card {
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 24px;
margin-bottom: 30px;
border: 1px solid #e9ecef;
}
.overall-content-wrapper {
display: flex;
gap: 20px;
margin-top: 20px;
align-items: center;
}
.overall-chart-left {
flex: 0 0 70%;
min-height: 300px;
display: flex;
align-items: center;
justify-content: center;
}
.overall-radar-right {
flex: 0 0 30%;
min-height: 280px;
display: flex;
align-items: center;
justify-content: center;
}
.overall-radar-right canvas {
max-width: 100%;
max-height: 100%;
}
/* Shot Count Cards */
.shot-count-cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(70px, 1fr));
gap: 12px;
margin-top: 25px;
padding-top: 20px;
border-top: 1px solid #e9ecef;
}
.shot-count-card {
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
border: 1px solid #e9ecef;
border-radius: 10px;
padding: 12px;
text-align: center;
transition: all 0.3s ease;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 90px;
}
.shot-count-card:hover {
box-shadow: 0 4px 12px rgba(40, 167, 69, 0.15);
transform: translateY(-2px);
border-color: #28a745;
}
.shot-score {
font-size: 1.3rem;
font-weight: bold;
color: #28a745;
margin-bottom: 8px;
}
.shot-count {
font-size: 1.8rem;
font-weight: bold;
color: #333;
}
.history-list {
flex: 1;
overflow-y: auto;
padding-right: 8px;
min-height: 200px;
max-height: 320px;
}
/* Custom scrollbar */
.history-list::-webkit-scrollbar {
width: 6px;
}
.history-list::-webkit-scrollbar-track {
background: #f8f9fa;
border-radius: 3px;
}
.history-list::-webkit-scrollbar-thumb {
background: #dee2e6;
border-radius: 3px;
}
.history-list::-webkit-scrollbar-thumb:hover {
background: #adb5bd;
}
/* Tournament History Items */
.history-item {
background: linear-gradient(135deg, #f8f9fa 0%, #e8f4fd 100%);
border-radius: 12px;
padding: 16px;
margin-bottom: 12px;
transition: all 0.3s ease;
border-left: 4px solid #2196f3;
display: flex;
justify-content: space-between;
align-items: center;
border: 1px solid rgba(33, 150, 243, 0.1);
position: relative;
overflow: hidden;
}
.history-item::before {
content: '';
position: absolute;
top: 0;
right: 0;
width: 3px;
height: 100%;
background: linear-gradient(135deg, #2196f3 0%, #64b5f6 100%);
opacity: 0;
transition: opacity 0.3s ease;
}
.history-item:hover::before {
opacity: 1;
}
.history-item:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(33, 150, 243, 0.15);
background: linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%);
border-color: rgba(33, 150, 243, 0.2);
}
/* Clickable History Item Links */
.history-item-link {
display: block;
cursor: pointer;
transition: all 0.3s ease;
color: inherit;
}
.history-item-link .history-item {
cursor: pointer;
}
.history-item-link:hover .history-item {
transform: translateY(-4px);
box-shadow: 0 12px 35px rgba(33, 150, 243, 0.35);
background: linear-gradient(135deg, #c5dff8 0%, #a5d6fd 100%);
border-color: rgba(33, 150, 243, 0.4);
}
/* Clickable League History Item Links */
.league-history-item-link {
display: block;
cursor: pointer;
transition: all 0.3s ease;
color: inherit;
}
.league-history-item-link .league-history-item {
cursor: pointer;
}
.league-history-item-link:hover .league-history-item {
transform: translateY(-4px);
box-shadow: 0 12px 35px rgba(156, 39, 176, 0.35);
background: linear-gradient(135deg, #d1a4e0 0%, #c878d8 100%);
border-color: rgba(156, 39, 176, 0.3);
}
.history-info {
flex: 1;
}
.history-date {
font-size: 0.8rem;
color: #1976d2;
margin-bottom: 2px;
font-weight: 600;
}
.history-type {
font-size: 0.75rem;
font-weight: bold;
color: #0d47a1;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.history-score {
font-size: 1.2rem;
font-weight: bold;
color: #2e7d32;
text-shadow: 0 1px 2px rgba(0,0,0,0.1);
}
/* League History Items */
.league-history-item {
background: linear-gradient(135deg, #f3e5f5 0%, #f0e6ff 100%);
border-radius: 12px;
padding: 16px;
margin-bottom: 12px;
transition: all 0.3s ease;
border-left: 4px solid #9c27b0;
border: 1px solid rgba(156, 39, 176, 0.1);
position: relative;
overflow: hidden;
}
.league-history-item::before {
content: '';
position: absolute;
top: 0;
right: 0;
width: 3px;
height: 100%;
background: linear-gradient(135deg, #9c27b0 0%, #ba68c8 100%);
opacity: 0;
transition: opacity 0.3s ease;
}
.league-history-item:hover::before {
opacity: 1;
}
.league-history-item:hover {
transform: translateY(-2px);
box-shadow: 0 8px 25px rgba(156, 39, 176, 0.15);
background: linear-gradient(135deg, #e1bee7 0%, #ce93d8 100%);
border-color: rgba(156, 39, 176, 0.2);
}
.league-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 8px;
}
.league-info {
flex: 1;
}
.league-score-display {
font-size: 1.3rem;
font-weight: bold;
color: #4a148c;
text-shadow: 0 1px 2px rgba(0,0,0,0.1);
}
.league-details {
background: linear-gradient(135deg, #e8f5e9 0%, #c8e6c9 100%);
border: 1px solid #81c784;
border-radius: 6px;
padding: 8px;
font-size: 0.7rem;
}
/* League Score Cell Base */
.league-score-cell {
padding: 8px 12px;
font-weight: 600;
font-size: 1rem;
}
/* Joker Tournament Styling */
.joker-tournament {
background-color: #fff3e0 !important;
border: 2px solid #ff9800 !important;
}
/* Excluded Tournament Styling */
.excluded-tournament {
background-color: #ffebee !important;
border: 2px dashed #f44336 !important;
}
/* Removed Score (Crossed Out) */
.removed-score {
color: #d32f2f;
text-decoration: line-through;
font-weight: bold;
}
.league-summary {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 6px;
font-weight: bold;
}
.league-final-score {
color: #1b5e20;
font-size: 0.85rem;
}
.league-total-score {
color: #388e3c;
font-size: 0.75rem;
}
.tournament-results {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 4px;
}
.tournament-result {
padding: 4px 6px;
border-radius: 4px;
font-size: 0.65rem;
font-weight: bold;
text-align: center;
transition: transform 0.2s ease;
}
.tournament-result:hover {
transform: scale(1.05);
}
.tournament-result.participated {
background: linear-gradient(135deg, #c8e6c9, #a5d6a7);
color: #1b5e20;
border: 1px solid #81c784;
}
.tournament-result.joker {
background: linear-gradient(135deg, #fff3e0, #ffcc02);
color: #e65100;
border: 1px solid #ffb74d;
}
/* Empty State */
.empty-state {
text-align: center;
padding: 20px;
color: #666;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 100%;
}
.empty-icon {
font-size: 2rem;
margin-bottom: 8px;
opacity: 0.5;
}
/* Responsive Design */
@media (max-width: 1200px) {
.top-section {
grid-template-columns: 1fr;
min-height: 320px;
}
}
@media (max-width: 768px) {
.navbar {
padding: 10px 15px;
flex-direction: column;
gap: 8px;
height: auto;
}
.navbar-controls {
flex-wrap: wrap;
justify-content: center;
}
.container {
min-height: calc(100vh - 100px);
padding: 10px;
}
.top-section, .bottom-section {
grid-template-columns: 1fr;
}
.stats-grid {
grid-template-columns: 1fr;
}
.top-section {
min-height: 250px;
}
.bottom-section {
min-height: 500px;
max-height: none;
}
.history-section {
min-height: 220px;
}
.history-list {
min-height: 150px;
max-height: 200px;
}
.chart-container {
max-height: 350px;
min-height: 300px;
padding: 10px;
}
/* Responsive Shot Count Cards */
.shot-count-cards {
grid-template-columns: repeat(auto-fit, minmax(60px, 1fr));
gap: 8px;
margin-top: 20px;
padding-top: 15px;
}
.shot-count-card {
min-height: 80px;
padding: 10px;
}
.shot-score {
font-size: 1.1rem;
margin-bottom: 5px;
}
.shot-count {
font-size: 1.5rem;
}
/* Responsive Overall Accuracy Dashboard */
.overall-top-section {
flex-direction: column;
gap: 15px;
margin-bottom: 15px;
}
.gauge-section {
flex: 0 0 100%;
min-height: 250px;
}
.stat-card-group {
flex: 0 0 100%;
grid-template-columns: 2fr 2fr;
gap: 10px;
}
.overall-bottom-section {
grid-template-columns: 1fr;
gap: 15px;
margin-top: 15px;
}
.overall-bar-chart,
.overall-radar-chart {
min-height: 280px;
padding: 10px;
}
.stat-card {
padding: 10px;
}
.stat-card-icon {
font-size: 1.4rem;
margin-bottom: 4px;
}
.stat-card-value {
font-size: 1.2rem;
margin-bottom: 2px;
}
.stat-card-label {
font-size: 0.65rem;
}
.gauge-stats {
gap: 15px;
padding: 8px 12px;
font-size: 0.8rem;
}
.gauge-stat-value {
font-size: 1.1rem;
}
.accuracy-gauge-wrapper {
height: 250px;
}
}
/* ── SINGLE CARD LAYOUT ── */
body { overflow: hidden; }
.ps-layout {
display: flex; gap: 8px;
height: calc(100vh - 50px);
padding: 8px 12px 10px;
}
body.in-iframe .ps-layout { height: 100vh; }
.ps-card {
background: white; border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
border: 1px solid #e9ecef;
overflow: hidden; display: flex; flex-direction: column;
}
.ps-left {
flex: 1; min-width: 0;
}
/* Card header */
.ps-topbar {
display: flex; align-items: center;
padding: 6px 12px; flex-shrink: 0; gap: 8px;
background: #f8f9fa; border-bottom: 2px solid #e9ecef;
border-radius: 10px 10px 0 0;
}
.ps-type-btns { display: flex; gap: 4px; flex-shrink: 0; }
.ps-type-btns .type-btn {
padding: 7px 10px; font-size: 0.7rem; border-radius: 6px;
border: 2px solid #dee2e6; background: white; color: #666;
cursor: pointer; font-weight: 600; transition: all 0.15s; white-space: nowrap;
flex-shrink: 0; width: auto;
}
.ps-type-btns .type-btn.active.targets-40 { background: #8b00ff; border-color: #6a00cc; color: white; }
.ps-type-btns .type-btn.active.targets-20 { background: #fd7e14; border-color: #e06c0a; color: white; }
.ps-type-btns .type-btn.active.targets-4 { background: #28a745; border-color: #1e7e34; color: white; }
.ps-type-btns .type-btn:hover:not(.active) { border-color: #28a745; color: #28a745; }
.ps-mini-stats { display: flex; gap: 20px; margin-left: auto; }
.ps-mini-stats .chart-stat { text-align: center; }
.ps-mini-stats .chart-stat-value { font-size: 0.85rem; font-weight: 700; color: #28a745; line-height: 1.1; }
.ps-mini-stats .chart-stat-label { font-size: 0.6rem; color: #aaa; }
/* Graph sections */
.ps-sections { display: flex; flex-direction: column; flex: 1; min-height: 0; overflow: hidden; }
.ps-section {
display: flex; flex-direction: column; flex: 1; min-height: 0;
border-bottom: 1px solid #e9ecef; padding: 6px 14px 4px;
}
.ps-section:last-child { border-bottom: none; }
.ps-section-header {
display: flex; align-items: baseline; gap: 6px; flex-shrink: 0; margin-bottom: 2px;
}
.ps-section-title {
font-size: 0.7rem; font-weight: 700; color: #444; white-space: nowrap;
}
.ps-section-desc {
font-size: 0.6rem; color: #aaa;
}
.ps-section-body {
display: flex; gap: 8px; flex: 1; min-height: 0;
}
.ps-chart-wrap {
flex: 1; min-width: 0; min-height: 0; position: relative;
}
.ps-chart-wrap canvas { width: 100% !important; height: 100% !important; }
.ps-radar-wrap { flex: 0 0 30%; }
/* Shot count badges */
.ps-shot-counts {
flex: 0 0 72px; border-left: 1px solid #e9ecef;
display: flex; flex-direction: column; justify-content: space-around;
padding: 4px 8px; gap: 2px;
}
.ps-sc-row {
display: flex; align-items: center; justify-content: space-between;
gap: 4px; font-size: 0.62rem;
}
.ps-sc-badge {
width: 18px; height: 18px; border-radius: 5px;
display: flex; align-items: center; justify-content: center;
font-weight: 700; font-size: 0.6rem; color: white; flex-shrink: 0;
}
.ps-sc-count {
font-size: 0.68rem; font-weight: 600; color: #555; text-align: right;
}
/* Right: history column */
.ps-right {
width: 220px; flex-shrink: 0; display: flex; flex-direction: column;
}
.ps-hist-header {
display: flex; flex-direction: column; gap: 5px;
padding: 7px 10px; flex-shrink: 0;
background: #f8f9fa; border-bottom: 2px solid #e9ecef;
border-radius: 10px 10px 0 0;
}
.ps-hist-title { font-size: 0.78rem; font-weight: 700; color: #444; }
.ps-hist-tabs { display: flex; gap: 4px; }
.ps-hist-tabs .history-tab {
flex: 1; padding: 3px 6px; font-size: 0.65rem; border-radius: 6px;
border: 2px solid #dee2e6; background: white; color: #555;
cursor: pointer; font-weight: 600; transition: all 0.15s; white-space: nowrap;
}
.ps-hist-tabs .history-tab.active { background: #28a745; border-color: #1e7e34; color: white; }
.ps-hist-tabs .history-tab:hover:not(.active) { border-color: #28a745; color: #28a745; }
.ps-history-body { flex: 1; overflow-y: auto; min-height: 0; }
.ps-history-body::-webkit-scrollbar { width: 4px; }
.ps-history-body::-webkit-scrollbar-thumb { background: #d1d5db; border-radius: 2px; }
.ps-hist-item {
display: flex; align-items: center; gap: 6px;
padding: 7px 10px 7px 12px;
cursor: pointer; border-left: 3px solid transparent;
transition: all 0.15s; border-bottom: 1px solid #f5f5f5; font-size: 0.78rem;
}
.ps-hist-item:hover { background: #f0faf3; border-left-color: #28a745; }
.ps-hist-item.active { background: #28a745; border-left-color: #1e7e34; color: white; }
.ps-hist-date { color: #888; font-size: 0.68rem; flex-shrink: 0; }
.ps-hist-item.active .ps-hist-date { color: rgba(255,255,255,0.8); }
.ps-hist-info { flex: 1; min-width: 0; }
.ps-hist-score { font-weight: 700; color: #28a745; flex-shrink: 0; font-size: 0.88rem; }
.ps-hist-item.active .ps-hist-score { color: white; }
.ps-hist-arrow { color: #ddd; flex-shrink: 0; font-size: 0.72rem; }
.ps-league-scores { display: flex; gap: 3px; flex-wrap: wrap; }
.ps-ls { font-size: 0.68rem; padding: 1px 4px; border-radius: 4px; background: #f0f0f0; }
.ps-ls-joker { opacity: 0.5; }
.ps-ls-excl { text-decoration: line-through; color: #999; background: #fee; }
.history-tab-content { display: none; }
.history-tab-content.active { display: block; }
</style>
<script src="/static/js/i18n.js"></script>
</head>
<body>
<!-- Navigation Bar -->
<div class="navbar">
<div class="navbar-title">📊 {{ player.name }} - <span data-i18n="players.player_stats">Stats</span></div>
<div class="navbar-controls">
<a href="/archive/player-analysis" class="nav-btn active">👤 <span data-i18n="players.player_analysis">Player Analysis</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="ps-layout">
<!-- LEFT: stats card -->
<div class="ps-card ps-left">
<!-- Card header: filter buttons -->
<div class="ps-topbar ps-topbar-header">
<div class="tournament-type-buttons ps-type-btns">
<button class="type-btn active targets-40" data-type="40 Targets"><span>💪</span> 40T</button>
<button class="type-btn targets-20" data-type="20 Targets"><span></span> 20T</button>
<button class="type-btn targets-4" data-type="4 Targets"><span>🎯</span> 4T</button>
</div>
<div class="ps-mini-stats">
<div class="chart-stat"><div class="chart-stat-value" id="gameCount">0</div><div class="chart-stat-label">Iger</div></div>
<div class="chart-stat"><div class="chart-stat-value" id="bestScore">0</div><div class="chart-stat-label">Najboljši</div></div>
<div class="chart-stat"><div class="chart-stat-value" id="avgScore">0</div><div class="chart-stat-label">Povprečje</div></div>
</div>
</div>
<div class="ps-sections">
<!-- Section 1: Natančnost -->
<div class="ps-section">
<div class="ps-section-header">
<span class="ps-section-title">🎯 Natančnost strelov</span>
<span class="ps-section-desc">Kako pogosto zadeneš posamezno vrednost</span>
</div>
<div class="ps-section-body">
<div class="ps-chart-wrap"><canvas id="accuracyChart"></canvas></div>
</div>
</div>
<!-- Section 2: Napredek + Profil -->
<div class="ps-section">
<div class="ps-section-body">
<div style="display:flex; flex-direction:column; flex:1; min-width:0; min-height:0;">
<div class="ps-section-header">
<span class="ps-section-title">📈 Napredek</span>
<span class="ps-section-desc">Rezultati skozi čas po turnirjih</span>
</div>
<div class="ps-chart-wrap" style="flex:1; min-height:0;"><canvas id="tournamentChart"></canvas></div>
</div>
<div style="display:flex; flex-direction:column; flex:0 0 32%; min-height:0; border-left:1px solid #e9ecef; padding-left:10px;">
<div class="ps-section-header">
<span class="ps-section-title">🕸 Profil</span>
<span class="ps-section-desc">Razporeditev strelov</span>
</div>
<div class="ps-chart-wrap" style="flex:1; min-height:0;"><canvas id="accuracyRadarChart"></canvas></div>
</div>
</div>
</div>
</div>
</div><!-- ps-left card -->
<!-- RIGHT: history card -->
<div class="ps-card ps-right">
<div class="ps-hist-header">
<div class="ps-hist-title">📜 Zgodovina</div>
<div class="history-tabs ps-hist-tabs">
<button class="history-tab active" data-tab="tournaments">🎯 T</button>
<button class="history-tab" data-tab="leagues">🏆 L</button>
</div>
</div>
<div class="ps-history-body">
<!-- Tournament History -->
<div id="tournaments-tab" class="history-tab-content active">
{% if stats.tournament_history %}
{% for tournament in stats.tournament_history %}
<div class="ps-hist-item" onclick="window.location.href='/archive/tournament/{{ tournament.filename }}'">
<div class="ps-hist-date">{{ tournament.date[:10] if tournament.date != 'Unknown' else '?' }}</div>
<div class="ps-hist-info">
<span class="tournament-type-badge" data-tournament-type="{{ tournament.tournament_type }}">{{ tournament.tournament_type.replace('_targets','T') }}</span>
</div>
<div class="ps-hist-score">{{ tournament.score }}</div>
<div class="ps-hist-arrow"></div>
</div>
{% endfor %}
{% else %}
<div class="empty-state"><div class="empty-icon">🎯</div><div>Ni zgodovine</div></div>
{% endif %}
</div>
<!-- League History -->
<div id="leagues-tab" class="history-tab-content">
{% if stats.league_history %}
{% for league in stats.league_history %}
<div class="ps-hist-item" onclick="window.location.href='/archive/league/{{ league.filename }}'">
<div class="ps-hist-date">{{ league.date[:10] if league.date != 'Unknown' else '?' }}</div>
<div class="ps-hist-info ps-league-scores">
{% if league.tournament_results %}
{% for i in range(league.tournament_results|length) %}
{% set result = league.tournament_results[i] %}
{% set is_excluded = league.excluded_tournament and league.excluded_tournament == (i+1) %}
{% set is_joker = result.joker or not result.participated %}
<span class="ps-ls {% if is_joker %}ps-ls-joker{% elif is_excluded %}ps-ls-excl{% endif %}">
{% if is_joker %}🃏{% elif is_excluded %}<s>{{ result.score if result else '-' }}</s>{% else %}{{ result.score if result else '-' }}{% endif %}
</span>
{% endfor %}
{% endif %}
</div>
<div class="ps-hist-score">{{ league.final_score }}</div>
<div class="ps-hist-arrow"></div>
</div>
{% endfor %}
{% else %}
<div class="empty-state"><div class="empty-icon">🏆</div><div>Ni zgodovine</div></div>
{% endif %}
</div>
</div><!-- ps-history-body -->
</div><!-- ps-right card -->
</div><!-- ps-layout -->
<script>
// Player data from Flask
const playerStats = {{ stats|tojson }};
// Tournament type configuration
const tournamentTypes = {
'40 Targets': { color: '#9C27B0', icon: '💪', class: 'targets-40' },
'20 Targets': { color: '#FF9800', icon: '⚡', class: 'targets-20' },
'4 Targets': { color: '#4CAF50', icon: '🎯', class: 'targets-4' }
};
let currentChart = null;
let currentTournamentType = '40 Targets'; // Will be updated based on available data
let tournamentsByType = {};
let shotAccuracyData = {};
// Format date from YYYY-MM-DD to DD.MM.YY
function formatDate(dateString) {
if (!dateString || dateString === 'Unknown') return 'Unknown';
const parts = dateString.split('-');
if (parts.length !== 3) return dateString;
const year = parts[0].slice(-2); // Get last 2 digits of year
const month = parts[1];
const day = parts[2];
return `${day}.${month}.${year}`;
}
// Translate tournament types in history tables
function translateTournamentTypes() {
const tournamentTypeBadges = document.querySelectorAll('.tournament-type-badge[data-tournament-type]');
tournamentTypeBadges.forEach(badge => {
const type = badge.getAttribute('data-tournament-type');
const translationKey = `tournament_types.${type}`;
// Use the global t() function from i18n.js
const translatedText = typeof t === 'function' ? t(translationKey) : type.replace(/_/g, ' ').toUpperCase();
badge.textContent = translatedText;
});
}
// Initialize page
function initializePage() {
groupTournamentsByType();
setupEventListeners();
loadShotAccuracyData();
formatDateCells();
populatePlacerStats();
populateOverallAccuracy();
translateTournamentTypes();
updateChart();
}
// Format all date cells in history tables
function formatDateCells() {
const dateCells = document.querySelectorAll('.date-cell');
dateCells.forEach(cell => {
const dateText = cell.textContent.trim();
cell.textContent = formatDate(dateText);
});
}
// Populate placer statistics card with total shot counts
function populatePlacerStats() {
const statsData = {{ stats|tojson }};
if (!statsData || !statsData.shot_accuracy) return;
// Aggregate all shot counts across all tournament types
const totalCounts = {
tens: 0, nines: 0, eights: 0, sevens: 0, sixes: 0,
fives: 0, fours: 0, threes: 0, twos: 0, ones: 0, zeros: 0
};
// Iterate through all tournament types and sum up the counts
Object.values(statsData.shot_accuracy).forEach(typeData => {
if (typeData.tens !== undefined) {
totalCounts.tens += typeData.tens || 0;
totalCounts.nines += typeData.nines || 0;
totalCounts.eights += typeData.eights || 0;
totalCounts.sevens += typeData.sevens || 0;
totalCounts.sixes += typeData.sixes || 0;
totalCounts.fives += typeData.fives || 0;
totalCounts.fours += typeData.fours || 0;
totalCounts.threes += typeData.threes || 0;
totalCounts.twos += typeData.twos || 0;
totalCounts.ones += typeData.ones || 0;
totalCounts.zeros += typeData.zeros || 0;
}
});
// Update the placer stat cards
const placerCards = document.querySelectorAll('.placer-stat-card');
const scoreMap = ['zeros', 'ones', 'twos', 'threes', 'fours', 'fives', 'sixes', 'sevens', 'eights', 'nines', 'tens'];
placerCards.forEach((card, index) => {
const key = scoreMap[index];
const countElement = card.querySelector('.placer-stat-count');
if (countElement) {
countElement.textContent = totalCounts[key] || 0;
}
});
}
// Populate overall shot accuracy card with all tournament data (no filters)
function populateOverallAccuracy() {
const statsData = {{ stats|tojson }};
if (!statsData || !statsData.shot_accuracy) return;
// Aggregate all shot counts across all tournament types
const totalCounts = {
tens: 0, nines: 0, eights: 0, sevens: 0, sixes: 0,
fives: 0, fours: 0, threes: 0, twos: 0, ones: 0, zeros: 0
};
// Iterate through all tournament types and sum up the counts
Object.values(statsData.shot_accuracy).forEach(typeData => {
if (typeData.tens !== undefined) {
totalCounts.tens += typeData.tens || 0;
totalCounts.nines += typeData.nines || 0;
totalCounts.eights += typeData.eights || 0;
totalCounts.sevens += typeData.sevens || 0;
totalCounts.sixes += typeData.sixes || 0;
totalCounts.fives += typeData.fives || 0;
totalCounts.fours += typeData.fours || 0;
totalCounts.threes += typeData.threes || 0;
totalCounts.twos += typeData.twos || 0;
totalCounts.ones += typeData.ones || 0;
totalCounts.zeros += typeData.zeros || 0;
}
});
// Create overall bar chart
createOverallAccuracyChart(totalCounts);
// Create overall pie chart
createOverallRadarChart(totalCounts);
// Populate shot count cards
populateShotCountCards(totalCounts);
}
// Populate shot count cards below charts
function populateShotCountCards(totalCounts) {
const scoreMap = {
10: totalCounts.tens,
9: totalCounts.nines,
8: totalCounts.eights,
7: totalCounts.sevens,
6: totalCounts.sixes,
5: totalCounts.fives,
4: totalCounts.fours,
3: totalCounts.threes,
2: totalCounts.twos,
1: totalCounts.ones,
0: totalCounts.zeros
};
// Update shot count cards
for (const [score, count] of Object.entries(scoreMap)) {
const countElement = document.getElementById(`count-${score}`);
if (countElement) {
countElement.textContent = count;
}
}
// Update top stat badges
updateTopStatsBadges(totalCounts);
}
// Update top stat badges with overall metrics
function updateTopStatsBadges(totalCounts) {
// Perfect 10s count
const totalTensBadge = document.getElementById('totalTens-badge');
if (totalTensBadge) {
totalTensBadge.textContent = totalCounts.tens;
}
// Calculate most common shot value
let mostCommonShot = 0;
let maxCount = 0;
const shotScores = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
const shotCountsArray = [
totalCounts.tens, totalCounts.nines, totalCounts.eights,
totalCounts.sevens, totalCounts.sixes, totalCounts.fives,
totalCounts.fours, totalCounts.threes, totalCounts.twos,
totalCounts.ones, totalCounts.zeros
];
for (let i = 0; i < shotScores.length; i++) {
if (shotCountsArray[i] > maxCount) {
maxCount = shotCountsArray[i];
mostCommonShot = shotScores[i];
}
}
const mostCommonBadge = document.getElementById('mostCommon-badge');
if (mostCommonBadge) {
mostCommonBadge.textContent = mostCommonShot;
}
}
// Create overall accuracy bar chart
let overallAccuracyChartInstance = null;
function createOverallAccuracyChart(totalCounts) {
const canvas = document.getElementById('overallAccuracyChart');
if (!canvas) {
console.warn('Overall accuracy chart canvas not found');
return;
}
const ctx = canvas.getContext('2d');
// Destroy existing chart if it exists
if (overallAccuracyChartInstance) {
overallAccuracyChartInstance.destroy();
}
const labels = ['10', '9', '8', '7', '6', '5', '4', '3', '2', '1', '0'];
const counts = [
totalCounts.tens, totalCounts.nines, totalCounts.eights, totalCounts.sevens,
totalCounts.sixes, totalCounts.fives, totalCounts.fours, totalCounts.threes,
totalCounts.twos, totalCounts.ones, totalCounts.zeros
];
// Use colorful array for overall accuracy - different color for each shot value
const colors = generateColorfulArray();
overallAccuracyChartInstance = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: 'Overall Shot Count',
data: counts,
backgroundColor: colors,
borderColor: colors,
borderWidth: 1,
borderRadius: 8
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
indexAxis: 'x',
plugins: {
legend: { display: false },
tooltip: {
backgroundColor: 'rgba(0,0,0,0.7)',
titleColor: '#fff',
bodyColor: '#fff',
padding: 10,
displayColors: false,
callbacks: {
title: function(context) {
return 'Shot Score: ' + context[0].label;
},
label: function(context) {
return 'Total Count: ' + context.parsed.y;
}
}
}
},
scales: {
y: {
beginAtZero: true,
ticks: {
stepSize: 1,
color: '#666',
font: {
size: 12
}
},
grid: {
color: '#e9ecef',
drawBorder: true
}
},
x: {
ticks: {
color: '#666',
font: {
size: 12,
weight: 'bold'
}
},
grid: {
display: false
}
}
}
}
});
console.log('Overall accuracy chart created successfully with data:', counts);
}
// Create overall pie chart
let overallRadarChartInstance = null;
function createOverallRadarChart(totalCounts) {
const canvas = document.getElementById('overallRadarChart');
if (!canvas) {
console.warn('Overall pie chart canvas not found');
return;
}
const ctx = canvas.getContext('2d');
// Destroy existing chart if it exists
if (overallRadarChartInstance) {
overallRadarChartInstance.destroy();
}
const labels = ['10', '9', '8', '7', '6', '5', '4', '3', '2', '1', '0'];
const data = [
totalCounts.tens, totalCounts.nines, totalCounts.eights, totalCounts.sevens,
totalCounts.sixes, totalCounts.fives, totalCounts.fours, totalCounts.threes,
totalCounts.twos, totalCounts.ones, totalCounts.zeros
];
// Use colorful array for pie chart
const colors = generateColorfulArray();
overallRadarChartInstance = new Chart(ctx, {
type: 'pie',
data: {
labels: labels.map(label => `Score ${label}`),
datasets: [{
label: 'Shot Distribution',
data: data,
backgroundColor: colors,
borderColor: '#fff',
borderWidth: 2
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
plugins: {
legend: { display: false },
tooltip: {
callbacks: {
label: function(context) {
const label = context.label || '';
const value = context.parsed || 0;
const total = context.dataset.data.reduce((a, b) => a + b, 0);
const percentage = ((value / total) * 100).toFixed(1);
return `${label}: ${value} (${percentage}%)`;
}
}
}
}
}
});
}
// Create accuracy gauge chart (doughnut showing overall accuracy percentage)
let accuracyGaugeChartInstance = null;
function createAccuracyGaugeChart(totalCounts) {
const canvas = document.getElementById('accuracyGaugeChart');
if (!canvas) {
console.warn('Accuracy gauge chart canvas not found');
return;
}
const ctx = canvas.getContext('2d');
// Destroy existing chart if it exists
if (accuracyGaugeChartInstance) {
accuracyGaugeChartInstance.destroy();
}
// Calculate accuracy percentage (perfect shots 8-10 / total shots)
const perfectShots = totalCounts.tens + totalCounts.nines + totalCounts.eights;
const totalShots = Object.values(totalCounts).reduce((a, b) => a + b, 0);
const accuracyPercentage = totalShots > 0 ? Math.round((perfectShots / totalShots) * 100) : 0;
// Calculate consistency score (lower variance = higher consistency)
const shotValues = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
const shotCounts = [totalCounts.tens, totalCounts.nines, totalCounts.eights, totalCounts.sevens, totalCounts.sixes, totalCounts.fives, totalCounts.fours, totalCounts.threes, totalCounts.twos, totalCounts.ones, totalCounts.zeros];
let mean = 0;
if (totalShots > 0) {
mean = shotValues.reduce((sum, val, i) => sum + (val * shotCounts[i]), 0) / totalShots;
}
let variance = 0;
if (totalShots > 0) {
variance = shotValues.reduce((sum, val, i) => sum + (Math.pow(val - mean, 2) * shotCounts[i]), 0) / totalShots;
}
const consistency = Math.max(0, Math.round(100 - (variance * 5))); // Scale variance to 0-100
// Update DOM with percentages
document.getElementById('accuracyPercentage').textContent = accuracyPercentage + '%';
document.getElementById('consistencyScore').textContent = consistency;
const gaugeData = [accuracyPercentage, 100 - accuracyPercentage];
accuracyGaugeChartInstance = new Chart(ctx, {
type: 'doughnut',
data: {
labels: ['Accuracy', 'Missed'],
datasets: [{
data: gaugeData,
backgroundColor: ['#28a745', '#f0f0f0'],
borderColor: ['#28a745', '#e0e0e0'],
borderWidth: 3,
circumference: 180,
rotation: 270
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: {
callbacks: {
label: function(context) {
return context.label + ': ' + context.parsed + '%';
}
}
}
}
}
});
}
// Update stat cards with performance metrics
function updateStatCards(statsData, totalCounts) {
try {
// Count tournaments
const tournaments = statsData.tournament_history ? statsData.tournament_history.length : 0;
document.getElementById('totalTournaments').textContent = tournaments;
// Count total 10s
const totalTens = totalCounts.tens || 0;
document.getElementById('totalTens').textContent = totalTens;
// Calculate variance
const shotValues = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0];
const shotCounts = [totalCounts.tens, totalCounts.nines, totalCounts.eights, totalCounts.sevens, totalCounts.sixes, totalCounts.fives, totalCounts.fours, totalCounts.threes, totalCounts.twos, totalCounts.ones, totalCounts.zeros];
const totalShots = shotCounts.reduce((a, b) => a + b, 0);
let mean = 0;
if (totalShots > 0) {
mean = shotValues.reduce((sum, val, i) => sum + (val * shotCounts[i]), 0) / totalShots;
}
let variance = 0;
if (totalShots > 0) {
variance = shotValues.reduce((sum, val, i) => sum + (Math.pow(val - mean, 2) * shotCounts[i]), 0) / totalShots;
}
document.getElementById('scoreVariance').textContent = variance.toFixed(2);
// Calculate range (highest - lowest non-zero score)
let minScore = null;
let maxScore = null;
for (let i = 0; i < shotValues.length; i++) {
if (shotCounts[i] > 0) {
if (maxScore === null) maxScore = shotValues[i];
minScore = shotValues[i];
}
}
const range = maxScore !== null ? (maxScore - minScore) : 0;
document.getElementById('scoreRange').textContent = range;
} catch (error) {
console.error('Error updating stat cards:', error);
}
}
// Load shot accuracy data from template context
function loadShotAccuracyData() {
try {
// Get shot accuracy data directly from template context
const statsData = {{ stats|tojson }};
console.log('Stats data from template:', statsData);
if (statsData && statsData.shot_accuracy) {
shotAccuracyData = statsData.shot_accuracy;
console.log('Shot accuracy data loaded from template:', shotAccuracyData);
// Map backend keys to display names
const typeMapping = {
'40_targets': '40 Targets',
'20_targets': '20 Targets',
'4_targets': '4 Targets'
};
// Auto-select the first tournament type with data
const availableTypes = Object.keys(shotAccuracyData);
if (availableTypes.length > 0) {
// Find the first type with actual data
for (const backendType of availableTypes) {
const typeData = shotAccuracyData[backendType];
const hasData = Object.values(typeData).some(v => v > 0);
if (hasData) {
// Convert backend key to display name
currentTournamentType = typeMapping[backendType] || backendType;
console.log('Selected tournament type:', currentTournamentType, 'from backend key:', backendType);
break;
}
}
// Update button states
updateActiveButton();
}
updateChart(); // Refresh chart with accuracy data
} else {
console.log('No shot accuracy data available in template');
updateChart();
}
} catch (error) {
console.error('Error loading shot accuracy data:', error);
updateChart();
}
}
// Update active button based on current tournament type
function updateActiveButton() {
const typeButtons = document.querySelectorAll('.type-btn');
typeButtons.forEach(btn => {
btn.classList.remove('active');
if (btn.dataset.type === currentTournamentType) {
btn.classList.add('active');
}
});
}
// Setup event listeners
function setupEventListeners() {
const typeButtons = document.querySelectorAll('.type-btn');
typeButtons.forEach(btn => {
btn.addEventListener('click', () => {
// Update active button
typeButtons.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
// Update current type and chart
currentTournamentType = btn.dataset.type;
updateChart();
});
});
// History tab switching
const historyTabs = document.querySelectorAll('.history-tab');
historyTabs.forEach(tab => {
tab.addEventListener('click', () => {
const tabName = tab.dataset.tab;
// Update active tab button
historyTabs.forEach(t => t.classList.remove('active'));
tab.classList.add('active');
// Update active tab content
const tabContents = document.querySelectorAll('.history-tab-content');
tabContents.forEach(content => content.classList.remove('active'));
document.getElementById(tabName + '-tab').classList.add('active');
});
});
}
// Group tournaments by type based on tournament_type field or shots fired
function groupTournamentsByType() {
const tournamentHistory = playerStats.tournament_history || [];
tournamentsByType = {};
tournamentHistory.forEach(tournament => {
let type;
// First try to use the tournament_type field if it exists
if (tournament.tournament_type) {
const typeField = tournament.tournament_type.toLowerCase();
if (typeField.includes('40') || typeField.includes('forty')) {
type = '40 Targets';
} else if (typeField.includes('20') || typeField.includes('twenty')) {
type = '20 Targets';
} else if (typeField.includes('4') || typeField.includes('four')) {
type = '4 Targets';
}
}
// If we couldn't determine from tournament_type, fall back to shots_fired
if (!type) {
const shots = tournament.shots_fired;
if (shots >= 30) {
type = '40 Targets';
} else if (shots >= 10 && shots <= 29) {
type = '20 Targets';
} else if (shots <= 9) {
type = '4 Targets';
}
}
// Skip tournaments that don't match our types
if (!type) {
console.log('Could not categorize tournament:', tournament);
return;
}
if (!tournamentsByType[type]) {
tournamentsByType[type] = [];
}
tournamentsByType[type].push(tournament);
});
// Sort each type by date
Object.keys(tournamentsByType).forEach(type => {
tournamentsByType[type].sort((a, b) => new Date(a.date) - new Date(b.date));
});
console.log('Tournaments grouped by type:', tournamentsByType);
console.log('Available tournament types from database:', Object.keys(tournamentsByType));
// If no 40 Targets tournaments, try to default to an available type
if (!tournamentsByType['40 Targets'] && Object.keys(tournamentsByType).length > 0) {
const availableTypes = Object.keys(tournamentsByType);
currentTournamentType = availableTypes[0];
console.log(`No 40 Targets tournaments found. Defaulting to: ${currentTournamentType}`);
updateActiveButton();
}
}
// Update chart and statistics for current tournament type
function updateChart() {
const tournaments = tournamentsByType[currentTournamentType] || [];
updateChartInfo(tournaments);
createChart(tournaments);
}
// Update chart information and statistics
function updateChartInfo(tournaments) {
const gameCount = tournaments.length;
const scores = tournaments.map(t => t.score);
const avgScore = gameCount > 0 ? Math.round(scores.reduce((a, b) => a + b, 0) / gameCount) : 0;
const bestScore = gameCount > 0 ? Math.max(...scores) : 0;
// Calculate most common shot value
let mostCommonShot = 0;
const shotCounts = { 10: 0, 9: 0, 8: 0, 7: 0, 6: 0, 5: 0, 4: 0, 3: 0, 2: 0, 1: 0, 0: 0 };
tournaments.forEach(tournament => {
if (tournament.shot_breakdown) {
const breakdown = tournament.shot_breakdown;
shotCounts[10] += breakdown.tens || 0;
shotCounts[9] += breakdown.nines || 0;
shotCounts[8] += breakdown.eights || 0;
shotCounts[7] += breakdown.sevens || 0;
shotCounts[6] += breakdown.sixes || 0;
shotCounts[5] += breakdown.fives || 0;
shotCounts[4] += breakdown.fours || 0;
shotCounts[3] += breakdown.threes || 0;
shotCounts[2] += breakdown.twos || 0;
shotCounts[1] += breakdown.ones || 0;
shotCounts[0] += breakdown.zeros || 0;
}
});
// Find shot value with highest count
let maxCount = 0;
for (const [score, count] of Object.entries(shotCounts)) {
if (count > maxCount) {
maxCount = count;
mostCommonShot = parseInt(score);
}
}
// Update basic stats
document.getElementById('gameCount').textContent = gameCount;
document.getElementById('bestScore').textContent = bestScore;
document.getElementById('avgScore').textContent = avgScore;
const mcEl = document.getElementById('mostCommonShot'); if (mcEl) mcEl.textContent = mostCommonShot;
// Update shot accuracy stats (if available in tournament data)
updateAccuracyStats(tournaments);
}
// Update shot accuracy statistics
function renderShotCountTable(counts) {
const el = document.getElementById('shotCountTable');
if (!el) return;
const labels = [10,9,8,7,6,5,4,3,2,1,0];
const colors = [
'#1a7a3c','#2d9e54','#3db86a','#5ec47c','#88d4a0',
'#b0dfc0','#c5c5c5','#e0a89a','#e07a6e','#d95050','#c0392b'
];
el.innerHTML = labels.map((lbl, i) => `
<div class="ps-sc-row">
<div class="ps-sc-badge" style="background:${colors[i]}">${lbl}</div>
<div class="ps-sc-count">${counts[i]}</div>
</div>`).join('');
}
function updateAccuracyStats(tournaments) {
let tens = 0, nines = 0, eights = 0, sevens = 0, sixes = 0;
let fives = 0, fours = 0, threes = 0, twos = 0, ones = 0, zeros = 0;
let hasData = false;
tournaments.forEach(tournament => {
// Check if we have shot breakdown data for this tournament
if (tournament.shot_breakdown) {
hasData = true;
const breakdown = tournament.shot_breakdown;
tens += breakdown.tens || 0;
nines += breakdown.nines || 0;
eights += breakdown.eights || 0;
sevens += breakdown.sevens || 0;
sixes += breakdown.sixes || 0;
fives += breakdown.fives || 0;
fours += breakdown.fours || 0;
threes += breakdown.threes || 0;
twos += breakdown.twos || 0;
ones += breakdown.ones || 0;
zeros += breakdown.zeros || 0;
}
});
// Also check if we have aggregated shot accuracy data for this tournament type
console.log('Current tournament type:', currentTournamentType);
console.log('Available keys in shotAccuracyData:', Object.keys(shotAccuracyData));
// Map display names to backend keys
const typeMapping = {
'40 Targets': '40_targets',
'20 Targets': '20_targets',
'4 Targets': '4_targets'
};
const backendKey = typeMapping[currentTournamentType] || currentTournamentType;
console.log('Looking for data with key:', backendKey);
if (shotAccuracyData && shotAccuracyData[backendKey]) {
hasData = true;
const typeData = shotAccuracyData[backendKey];
console.log('Found shot accuracy data for type:', typeData);
tens = typeData.tens || 0;
nines = typeData.nines || 0;
eights = typeData.eights || 0;
sevens = typeData.sevens || 0;
sixes = typeData.sixes || 0;
fives = typeData.fives || 0;
fours = typeData.fours || 0;
threes = typeData.threes || 0;
twos = typeData.twos || 0;
ones = typeData.ones || 0;
zeros = typeData.zeros || 0;
} else {
console.log('No shot accuracy data found for type:', currentTournamentType, 'mapped to:', backendKey);
console.log('Full shotAccuracyData object:', shotAccuracyData);
}
if (!hasData) {
console.log(`No shot accuracy data available for ${currentTournamentType}`);
return;
}
// Render accuracy bar chart
createAccuracyChart(tens, nines, eights, sevens, sixes, fives, fours, threes, twos, ones, zeros);
// Render shot count table
renderShotCountTable([tens, nines, eights, sevens, sixes, fives, fours, threes, twos, ones, zeros]);
console.log(`Shot accuracy for ${currentTournamentType}:`, {
tens, nines, eights, sevens, sixes, fives, fours, threes, twos, ones, zeros
});
}
// Generate solid color array - all bars same color based on tournament type
function generateColorGradient(baseColor, numColors) {
// Return array with same color for all bars
const colors = [];
for (let i = 0; i < numColors; i++) {
colors.push(baseColor);
}
return colors;
}
// Generate colorful array for overall accuracy - different color for each shot value
function generateColorfulArray() {
// Color mapping from green (perfect 10) to red (misses 0): labels are [10,9,8,7,6,5,4,3,2,1,0]
return [
'#4A9D6F', // 10 - medium green (perfect)
'#5BA97A', // 9 - medium green
'#6CB585', // 8 - medium green
'#7DC285', // 7 - medium light green
'#8FCC7F', // 6 - medium lime green
'#D4B84D', // 5 - medium yellow
'#CDA642', // 4 - medium golden yellow
'#D99A5D', // 3 - medium orange
'#D87A6C', // 2 - medium light orange-red
'#C85C5C', // 1 - medium red
'#B84A4A' // 0 - medium dark red (miss)
];
}
// Create bar chart for shot accuracy distribution using Chart.js
let accuracyChartInstance = null;
function createAccuracyChart(tens, nines, eights, sevens, sixes, fives, fours, threes, twos, ones, zeros) {
const canvas = document.getElementById('accuracyChart');
if (!canvas) {
console.warn('Accuracy chart canvas not found');
return;
}
const ctx = canvas.getContext('2d');
// Destroy existing chart if it exists
if (accuracyChartInstance) {
accuracyChartInstance.destroy();
}
const labels = ['10', '9', '8', '7', '6', '5', '4', '3', '2', '1', '0'];
const counts = [tens, nines, eights, sevens, sixes, fives, fours, threes, twos, ones, zeros];
// Get the theme color for current tournament type
const typeConfig = tournamentTypes[currentTournamentType];
const baseColor = typeConfig.color;
// Generate gradient colors from lighter to darker
const colors = generateColorGradient(baseColor, 11); // 11 bars for 0-10
accuracyChartInstance = new Chart(ctx, {
type: 'bar',
data: {
labels: labels,
datasets: [{
label: 'Shot Count',
data: counts,
backgroundColor: colors,
borderColor: colors,
borderWidth: 1,
borderRadius: 8
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
indexAxis: 'x',
plugins: {
legend: { display: false },
tooltip: {
backgroundColor: 'rgba(0,0,0,0.7)',
titleColor: '#fff',
bodyColor: '#fff',
padding: 10,
displayColors: false,
callbacks: {
title: function(context) {
return 'Shot Score: ' + context[0].label;
},
label: function(context) {
return 'Count: ' + context.parsed.y;
}
}
}
},
scales: {
y: {
beginAtZero: true,
ticks: {
stepSize: 1,
color: '#666',
font: {
size: 12
}
},
grid: {
color: '#e9ecef',
drawBorder: true
}
},
x: {
ticks: {
color: '#666',
font: {
size: 12,
weight: 'bold'
}
},
grid: {
display: false
}
}
}
}
});
console.log('Bar chart created successfully with data:', {tens, nines, eights, sevens, sixes, fives, fours, threes, twos, ones, zeros});
// Create radar chart as well
createAccuracyRadarChart(tens, nines, eights, sevens, sixes, fives, fours, threes, twos, ones, zeros);
}
// Create radar chart for shot accuracy
let accuracyRadarChartInstance = null;
function createAccuracyRadarChart(tens, nines, eights, sevens, sixes, fives, fours, threes, twos, ones, zeros) {
const canvas = document.getElementById('accuracyRadarChart');
if (!canvas) {
console.warn('Accuracy radar chart canvas not found');
return;
}
const ctx = canvas.getContext('2d');
// Destroy existing chart if it exists
if (accuracyRadarChartInstance) {
accuracyRadarChartInstance.destroy();
}
const labels = ['10', '9', '8', '7', '6', '5', '4', '3', '2', '1', '0'];
const counts = [tens, nines, eights, sevens, sixes, fives, fours, threes, twos, ones, zeros];
const typeConfig = tournamentTypes[currentTournamentType];
accuracyRadarChartInstance = new Chart(ctx, {
type: 'radar',
data: {
labels: labels,
datasets: [{
label: 'Shot Count',
data: counts,
fill: true,
backgroundColor: typeConfig.color + '30',
borderColor: typeConfig.color,
borderWidth: 2,
pointRadius: 5,
pointBackgroundColor: typeConfig.color,
pointBorderColor: '#fff',
pointBorderWidth: 2,
pointHoverRadius: 7
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false }
},
scales: {
r: {
beginAtZero: true,
ticks: {
color: '#666',
font: { size: 10 }
},
grid: {
color: '#e9ecef'
}
}
}
}
});
console.log('Radar chart created successfully with data:', {tens, nines, eights, sevens, sixes, fives, fours, threes, twos, ones, zeros});
}
// Create chart for current tournament type
function createChart(tournaments) {
const canvas = document.getElementById('tournamentChart');
const ctx = canvas.getContext('2d');
// Destroy existing chart if it exists
if (currentChart) {
currentChart.destroy();
}
if (tournaments.length === 0) {
// Show no data message
ctx.font = '16px Arial';
ctx.fillStyle = '#666';
ctx.textAlign = 'center';
ctx.fillText(`No ${currentTournamentType} tournaments found`, canvas.width / 2, canvas.height / 2);
return;
}
const typeConfig = tournamentTypes[currentTournamentType];
const scores = tournaments.map(t => t.score);
currentChart = new Chart(ctx, {
type: 'line',
data: {
labels: scores.map((_, i) => `${i + 1}`),
datasets: [{
label: `${currentTournamentType} Score`,
data: scores,
borderColor: typeConfig.color,
backgroundColor: typeConfig.color + '20',
borderWidth: 5,
fill: true,
tension: 0.4,
pointRadius: 6,
pointHoverRadius: 10,
pointBackgroundColor: typeConfig.color,
pointBorderColor: '#fff',
pointBorderWidth: 3,
pointHoverBackgroundColor: typeConfig.color,
pointHoverBorderColor: '#fff'
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: {
backgroundColor: typeConfig.color + 'E6',
titleColor: '#fff',
bodyColor: '#fff',
borderColor: typeConfig.color,
borderWidth: 2,
callbacks: {
title: function(context) {
const tournament = tournaments[context[0].dataIndex];
return `Game ${context[0].dataIndex + 1} - ${tournament.date.split(' ')[0]}`;
},
label: function(context) {
const tournament = tournaments[context.dataIndex];
const labels = [`Score: ${context.parsed.y}`, `Shots: ${tournament.shots_fired}`];
// Add tournament type if available
if (tournament.tournament_type) {
labels.push(`Type: ${tournament.tournament_type.replace('_', ' ')}`);
}
// Add shot breakdown if available
if (tournament.shot_breakdown) {
const breakdown = tournament.shot_breakdown;
const shots = [];
if (breakdown.tens) shots.push(`10s: ${breakdown.tens}`);
if (breakdown.nines) shots.push(`9s: ${breakdown.nines}`);
if (breakdown.eights) shots.push(`8s: ${breakdown.eights}`);
if (breakdown.sevens) shots.push(`7s: ${breakdown.sevens}`);
if (breakdown.sixes) shots.push(`6s: ${breakdown.sixes}`);
if (breakdown.fives) shots.push(`5s: ${breakdown.fives}`);
if (breakdown.fours) shots.push(`4s: ${breakdown.fours}`);
if (breakdown.threes) shots.push(`3s: ${breakdown.threes}`);
if (breakdown.twos) shots.push(`2s: ${breakdown.twos}`);
if (breakdown.ones) shots.push(`1s: ${breakdown.ones}`);
if (breakdown.zeros) shots.push(`0s: ${breakdown.zeros}`);
if (shots.length > 0) {
// Show only top scoring shots to keep tooltip readable
const topShots = shots.slice(0, 5);
labels.push(topShots.join(', '));
if (shots.length > 5) {
labels.push('+ more...');
}
}
}
return labels;
}
}
}
},
scales: {
y: {
beginAtZero: true,
grid: {
color: typeConfig.color + '20'
},
ticks: {
color: typeConfig.color,
font: { size: 12, weight: 'bold' }
}
},
x: {
grid: {
color: typeConfig.color + '20'
},
ticks: {
color: typeConfig.color,
font: { size: 12, weight: 'bold' }
}
}
},
interaction: {
intersect: false,
mode: 'index'
}
}
});
}
if (window.self !== window.top) document.body.classList.add('in-iframe');
// Initialize page when i18n is ready
window.addEventListener('i18nReady', initializePage);
</script>
</body>
</html>