Push latest fixes
This commit is contained in:
parent
a6febae8a8
commit
09d5c79e8d
@ -347,7 +347,7 @@ def vote_thanks():
|
||||
motm_name = motm_player[0]['playernickname'] if motm_player else f'Player {_motm}'
|
||||
dotd_name = dotd_player[0]['playernickname'] if dotd_player else f'Player {_dotd}'
|
||||
|
||||
# Update MOTM vote - use PostgreSQL UPSERT syntax (don't update totals)
|
||||
# Update MOTM vote - use PostgreSQL UPSERT syntax
|
||||
sql_motm = text(f"""
|
||||
INSERT INTO _hkfc_c_motm (playernumber, playername, {motm_col})
|
||||
VALUES (:player_num, :player_name, 1)
|
||||
@ -356,7 +356,7 @@ def vote_thanks():
|
||||
""")
|
||||
sql_write(sql_motm, {'player_num': _motm, 'player_name': motm_name})
|
||||
|
||||
# Update DotD vote - use PostgreSQL UPSERT syntax (don't update totals)
|
||||
# Update DotD vote - use PostgreSQL UPSERT syntax
|
||||
sql_dotd = text(f"""
|
||||
INSERT INTO _hkfc_c_motm (playernumber, playername, {dotd_col})
|
||||
VALUES (:player_num, :player_name, 1)
|
||||
@ -365,6 +365,32 @@ def vote_thanks():
|
||||
""")
|
||||
sql_write(sql_dotd, {'player_num': _dotd, 'player_name': dotd_name})
|
||||
|
||||
# Recalculate totals for both players
|
||||
def update_player_totals(player_num):
|
||||
# Get player data
|
||||
sql_player = text("SELECT * FROM _hkfc_c_motm WHERE playernumber = :player_num")
|
||||
player_data = sql_read(sql_player, {'player_num': player_num})
|
||||
|
||||
if player_data:
|
||||
player = player_data[0]
|
||||
motm_total = 0
|
||||
dotd_total = 0
|
||||
|
||||
# Calculate totals from fixture columns
|
||||
for col_name in player.keys():
|
||||
if col_name.startswith('motm_') and col_name != 'motmtotal':
|
||||
motm_total += player[col_name] or 0
|
||||
elif col_name.startswith('dotd_') and col_name != 'dotdtotal':
|
||||
dotd_total += player[col_name] or 0
|
||||
|
||||
# Update stored totals
|
||||
sql_update = text("UPDATE _hkfc_c_motm SET motmtotal = :motm_total, dotdtotal = :dotd_total WHERE playernumber = :player_num")
|
||||
sql_write(sql_update, {'motm_total': motm_total, 'dotd_total': dotd_total, 'player_num': player_num})
|
||||
|
||||
# Update totals for both players
|
||||
update_player_totals(_motm)
|
||||
update_player_totals(_dotd)
|
||||
|
||||
# Generate device identifier and record vote for tracking
|
||||
device_id = generate_device_id(request)
|
||||
sql_device = text("""
|
||||
|
||||
@ -1 +0,0 @@
|
||||
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==
|
||||
@ -1,62 +1,315 @@
|
||||
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
|
||||
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
|
||||
<div id="chart_div" style="width: 800px; height: 1000px;"></div>
|
||||
{% extends "base.html" %}
|
||||
|
||||
<script type="text/javascript">
|
||||
{% block title %}Player of the Year - HKFC MOTM System{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Page Header -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<h1 class="card-title">
|
||||
<i class="fas fa-trophy text-warning me-2"></i>Player of the Year
|
||||
</h1>
|
||||
<p class="lead text-muted">MOTM and DotD vote standings</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
google.charts.load('current', {
|
||||
packages: ['corechart']
|
||||
}).then(function () {
|
||||
// create chart
|
||||
var container = $('#chart_div').get(0);
|
||||
var chart = new google.visualization.ColumnChart(container);
|
||||
var options = {
|
||||
legend: {
|
||||
position: 'top'
|
||||
}
|
||||
};
|
||||
<!-- Chart Container -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header bg-primary text-white">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-chart-bar me-2"></i>Current Standings
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="poty-chart-container">
|
||||
<!-- Chart will be loaded here -->
|
||||
<div class="text-center py-5">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<p class="mt-3 text-muted">Loading player data...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
// create data table
|
||||
var data = new google.visualization.DataTable();
|
||||
data.addColumn('string', 'Player');
|
||||
data.addColumn('number', 'MotM Total');
|
||||
data.addColumn('number', 'DotD Total');
|
||||
<!-- Legend -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title">
|
||||
<i class="fas fa-info-circle me-2"></i>Legend
|
||||
</h6>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<div class="motm-bar me-3" style="width: 20px; height: 20px; background: linear-gradient(135deg, #28a745, #20c997); border-radius: 4px;"></div>
|
||||
<span><strong>MOTM Votes</strong> - Man of the Match</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<div class="dotd-bar me-3" style="width: 20px; height: 20px; background: linear-gradient(135deg, #dc3545, #fd7e14); border-radius: 4px;"></div>
|
||||
<span><strong>DotD Votes</strong> - Dick of the Day</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
// get data
|
||||
$.ajax({
|
||||
url: '/api/poty-results',
|
||||
dataType: 'json'
|
||||
}).done(function (jsonData) {
|
||||
loadData(jsonData);
|
||||
}).fail(function (jqXHR, textStatus, errorThrown) {
|
||||
var jsonData = [{"motmTotal": 5, "playerName": "ERVINE Jonathan Desmond", "dotdTotal": 2}, {"motmTotal": 3, "playerName": "MCDONAGH Jerome Michael", "dotdTotal": 1}];
|
||||
loadData(jsonData);
|
||||
});
|
||||
<!-- Navigation -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-12 text-center">
|
||||
<a href="/admin" class="btn btn-primary">
|
||||
<i class="fas fa-arrow-left me-2"></i>Back to Admin
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
// load json data
|
||||
function loadData(jsonData) {
|
||||
if (jsonData.length === 0) {
|
||||
// Show message when no data
|
||||
$('#chart_div').html('<div class="alert alert-info text-center"><h4>No Vote Data Available</h4><p>No players have received MOTM or DotD votes yet.</p><p>Votes will appear here once players start receiving votes.</p></div>');
|
||||
return;
|
||||
}
|
||||
|
||||
$.each(jsonData, function(index, row) {
|
||||
data.addRow([
|
||||
row.playerName,
|
||||
row.motmTotal,
|
||||
row.dotdTotal
|
||||
]);
|
||||
});
|
||||
drawChart();
|
||||
}
|
||||
<style>
|
||||
.poty-player-card {
|
||||
background: #fff;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
// draw chart
|
||||
$(window).resize(drawChart);
|
||||
function drawChart() {
|
||||
chart.draw(data, options);
|
||||
}
|
||||
.poty-player-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.poty-player-name {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.poty-stats {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.poty-stat {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.poty-stat-value {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
min-width: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.poty-stat-label {
|
||||
font-size: 0.9rem;
|
||||
color: #6c757d;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.poty-bar-container {
|
||||
flex: 1;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.poty-bar {
|
||||
height: 8px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 4px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.poty-bar-motm {
|
||||
background: linear-gradient(90deg, #28a745, #20c997);
|
||||
}
|
||||
|
||||
.poty-bar-dotd {
|
||||
background: linear-gradient(90deg, #dc3545, #fd7e14);
|
||||
}
|
||||
|
||||
.poty-bar-fill {
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
transition: width 0.8s ease-in-out;
|
||||
}
|
||||
|
||||
.poty-bar-motm .poty-bar-fill {
|
||||
background: linear-gradient(90deg, #28a745, #20c997);
|
||||
}
|
||||
|
||||
.poty-bar-dotd .poty-bar-fill {
|
||||
background: linear-gradient(90deg, #dc3545, #fd7e14);
|
||||
}
|
||||
|
||||
.poty-no-data {
|
||||
text-align: center;
|
||||
padding: 3rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.poty-no-data i {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
color: #dee2e6;
|
||||
}
|
||||
|
||||
.poty-ranking {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
background: linear-gradient(135deg, #007bff, #0056b3);
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
font-weight: 700;
|
||||
font-size: 0.9rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.poty-ranking.gold {
|
||||
background: linear-gradient(135deg, #ffd700, #ffb347);
|
||||
}
|
||||
|
||||
.poty-ranking.silver {
|
||||
background: linear-gradient(135deg, #c0c0c0, #a8a8a8);
|
||||
}
|
||||
|
||||
.poty-ranking.bronze {
|
||||
background: linear-gradient(135deg, #cd7f32, #b8860b);
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadPOTYData();
|
||||
});
|
||||
|
||||
function loadPOTYData() {
|
||||
fetch('/api/poty-results')
|
||||
.then(response => {
|
||||
console.log('Response status:', response.status);
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log('Raw API data:', data);
|
||||
renderPOTYChart(data);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading POTY data:', error);
|
||||
renderNoData();
|
||||
});
|
||||
}
|
||||
|
||||
function renderPOTYChart(data) {
|
||||
const container = document.getElementById('poty-chart-container');
|
||||
|
||||
console.log('POTY Data received:', data);
|
||||
|
||||
if (!data || data.length === 0) {
|
||||
renderNoData();
|
||||
return;
|
||||
}
|
||||
|
||||
// Sort by MOTM votes (descending), then by DotD votes (ascending for better ranking)
|
||||
data.sort((a, b) => {
|
||||
if (b.motmTotal !== a.motmTotal) {
|
||||
return b.motmTotal - a.motmTotal;
|
||||
}
|
||||
return a.dotdTotal - b.dotdTotal;
|
||||
});
|
||||
|
||||
// Find max values for scaling - each bar scales independently
|
||||
const maxMotm = Math.max(...data.map(p => p.motmTotal || 0));
|
||||
const maxDotd = Math.max(...data.map(p => p.dotdTotal || 0));
|
||||
|
||||
let html = '';
|
||||
|
||||
data.forEach((player, index) => {
|
||||
console.log('Processing player:', player);
|
||||
const ranking = index + 1;
|
||||
const rankingClass = ranking === 1 ? 'gold' : ranking === 2 ? 'silver' : ranking === 3 ? 'bronze' : '';
|
||||
|
||||
html += '<div class="poty-player-card">';
|
||||
html += '<div class="d-flex align-items-center">';
|
||||
html += '<div class="poty-ranking ' + rankingClass + '">' + ranking + '</div>';
|
||||
html += '<div class="flex-grow-1">';
|
||||
html += '<div class="poty-player-name">' + (player.playerName || 'Unknown Player') + '</div>';
|
||||
html += '<div class="poty-stats">';
|
||||
html += '<div class="poty-stat">';
|
||||
html += '<div class="poty-stat-value text-success">' + (player.motmTotal || 0) + '</div>';
|
||||
html += '<div class="poty-stat-label">MOTM</div>';
|
||||
html += '</div>';
|
||||
html += '<div class="poty-stat">';
|
||||
html += '<div class="poty-stat-value text-danger">' + (player.dotdTotal || 0) + '</div>';
|
||||
html += '<div class="poty-stat-label">DotD</div>';
|
||||
html += '</div>';
|
||||
html += '<div class="poty-bar-container">';
|
||||
html += '<div class="poty-bar poty-bar-motm">';
|
||||
html += '<div class="poty-bar-fill" style="width: ' + (maxMotm > 0 ? ((player.motmTotal || 0) / maxMotm * 100) : 0) + '%"></div>';
|
||||
html += '</div>';
|
||||
html += '<div class="poty-bar poty-bar-dotd">';
|
||||
html += '<div class="poty-bar-fill" style="width: ' + (maxDotd > 0 ? ((player.dotdTotal || 0) / maxDotd * 100) : 0) + '%"></div>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
});
|
||||
|
||||
container.innerHTML = html;
|
||||
|
||||
// Animate bars after a short delay
|
||||
setTimeout(() => {
|
||||
const bars = container.querySelectorAll('.poty-bar-fill');
|
||||
bars.forEach(bar => {
|
||||
const width = bar.style.width;
|
||||
bar.style.width = '0%';
|
||||
setTimeout(() => {
|
||||
bar.style.width = width;
|
||||
}, 100);
|
||||
});
|
||||
}, 200);
|
||||
}
|
||||
|
||||
function renderNoData() {
|
||||
const container = document.getElementById('poty-chart-container');
|
||||
container.innerHTML = '<div class="poty-no-data">' +
|
||||
'<i class="fas fa-chart-line"></i>' +
|
||||
'<h4>No Vote Data Available</h4>' +
|
||||
'<p>No players have received MOTM or DotD votes yet.</p>' +
|
||||
'<p class="text-muted">Votes will appear here once players start receiving votes.</p>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
// Test function to verify basic functionality
|
||||
function testPOTYChart() {
|
||||
const testData = [
|
||||
{playerName: 'Test Player 1', motmTotal: 5, dotdTotal: 1},
|
||||
{playerName: 'Test Player 2', motmTotal: 3, dotdTotal: 2}
|
||||
];
|
||||
console.log('Testing with sample data:', testData);
|
||||
renderPOTYChart(testData);
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
@ -1,56 +1,330 @@
|
||||
<script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
|
||||
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
|
||||
<div id="chart_div" style="width: 800px; height: 1000px;"></div>
|
||||
{% extends "base.html" %}
|
||||
|
||||
<script type="text/javascript">
|
||||
{% block title %}Vote Results - HKFC MOTM System{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Page Header -->
|
||||
<div class="row mb-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body text-center">
|
||||
<h1 class="card-title">
|
||||
<i class="fas fa-chart-bar text-primary me-2"></i>Current Match Vote Results
|
||||
</h1>
|
||||
<p class="lead text-muted">MOTM and DotD votes for match on {{ _matchDate[:4] }}-{{ _matchDate[4:6] }}-{{ _matchDate[6:8] }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
google.charts.load('current', {
|
||||
packages: ['corechart']
|
||||
}).then(function () {
|
||||
// create chart
|
||||
var container = $('#chart_div').get(0);
|
||||
var chart = new google.visualization.ColumnChart(container);
|
||||
var options = {
|
||||
legend: {
|
||||
position: 'top'
|
||||
}
|
||||
};
|
||||
<!-- Chart Container -->
|
||||
<div class="row">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-header bg-success text-white">
|
||||
<h5 class="card-title mb-0">
|
||||
<i class="fas fa-poll me-2"></i>Vote Counts
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="vote-chart-container">
|
||||
<!-- Chart will be loaded here -->
|
||||
<div class="text-center py-5">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<p class="mt-3 text-muted">Loading vote data...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
// create data table
|
||||
var data = new google.visualization.DataTable();
|
||||
data.addColumn('string', 'Player');
|
||||
data.addColumn('number', 'MotM');
|
||||
data.addColumn('number', 'DotD');
|
||||
<!-- Legend -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-12">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<h6 class="card-title">
|
||||
<i class="fas fa-info-circle me-2"></i>Legend
|
||||
</h6>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<div class="motm-bar me-3" style="width: 20px; height: 20px; background: linear-gradient(135deg, #28a745, #20c997); border-radius: 4px;"></div>
|
||||
<span><strong>MOTM Votes</strong> - Man of the Match</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<div class="dotd-bar me-3" style="width: 20px; height: 20px; background: linear-gradient(135deg, #dc3545, #fd7e14); border-radius: 4px;"></div>
|
||||
<span><strong>DotD Votes</strong> - Dick of the Day</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
// get data
|
||||
$.ajax({
|
||||
url: '/api/vote-results',
|
||||
dataType: 'json'
|
||||
}).done(function (jsonData) {
|
||||
loadData(jsonData);
|
||||
}).fail(function (jqXHR, textStatus, errorThrown) {
|
||||
var jsonData = [{"motm_{{ _matchDate }}": 1, "playerName": "ERVINE Jonathan Desmond", "dotd_{{ _matchDate }}": 0}, {"motm_{{ _matchDate }}": 0, "playerName": "MCDONAGH Jerome Michael", "dotd_{{ _matchDate }}": 1}];
|
||||
loadData(jsonData);
|
||||
});
|
||||
<!-- Navigation -->
|
||||
<div class="row mt-4">
|
||||
<div class="col-12 text-center">
|
||||
<a href="/admin" class="btn btn-primary">
|
||||
<i class="fas fa-arrow-left me-2"></i>Back to Admin
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
// load json data
|
||||
function loadData(jsonData) {
|
||||
$.each(jsonData, function(index, row) {
|
||||
data.addRow([
|
||||
row.playerName,
|
||||
row.motm_{{ _matchDate }},
|
||||
row.dotd_{{ _matchDate }}
|
||||
]);
|
||||
});
|
||||
drawChart();
|
||||
}
|
||||
<style>
|
||||
.vote-player-card {
|
||||
background: #fff;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 8px;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
transition: transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
// draw chart
|
||||
$(window).resize(drawChart);
|
||||
function drawChart() {
|
||||
chart.draw(data, options);
|
||||
}
|
||||
.vote-player-card:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 4px 8px rgba(0,0,0,0.15);
|
||||
}
|
||||
|
||||
.vote-player-name {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.vote-stats {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.vote-stat {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.vote-stat-value {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 700;
|
||||
min-width: 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.vote-stat-label {
|
||||
font-size: 0.9rem;
|
||||
color: #6c757d;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.vote-bar-container {
|
||||
flex: 1;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
.vote-bar {
|
||||
height: 8px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 4px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.vote-bar-motm {
|
||||
background: linear-gradient(90deg, #28a745, #20c997);
|
||||
}
|
||||
|
||||
.vote-bar-dotd {
|
||||
background: linear-gradient(90deg, #dc3545, #fd7e14);
|
||||
}
|
||||
|
||||
.vote-bar-fill {
|
||||
height: 100%;
|
||||
border-radius: 4px;
|
||||
transition: width 0.8s ease-in-out;
|
||||
}
|
||||
|
||||
.vote-bar-motm .vote-bar-fill {
|
||||
background: linear-gradient(90deg, #28a745, #20c997);
|
||||
}
|
||||
|
||||
.vote-bar-dotd .vote-bar-fill {
|
||||
background: linear-gradient(90deg, #dc3545, #fd7e14);
|
||||
}
|
||||
|
||||
.vote-no-data {
|
||||
text-align: center;
|
||||
padding: 3rem;
|
||||
color: #6c757d;
|
||||
}
|
||||
|
||||
.vote-no-data i {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
color: #dee2e6;
|
||||
}
|
||||
|
||||
.vote-ranking {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
background: linear-gradient(135deg, #007bff, #0056b3);
|
||||
color: white;
|
||||
border-radius: 50%;
|
||||
font-weight: 700;
|
||||
font-size: 0.9rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.vote-ranking.gold {
|
||||
background: linear-gradient(135deg, #ffd700, #ffb347);
|
||||
}
|
||||
|
||||
.vote-ranking.silver {
|
||||
background: linear-gradient(135deg, #c0c0c0, #a8a8a8);
|
||||
}
|
||||
|
||||
.vote-ranking.bronze {
|
||||
background: linear-gradient(135deg, #cd7f32, #b8860b);
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadVoteData();
|
||||
});
|
||||
|
||||
function loadVoteData() {
|
||||
fetch('/api/vote-results')
|
||||
.then(response => {
|
||||
console.log('Vote response status:', response.status);
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
console.log('Raw vote API data:', data);
|
||||
renderVoteChart(data);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error loading vote data:', error);
|
||||
renderNoData();
|
||||
});
|
||||
}
|
||||
|
||||
function renderVoteChart(data) {
|
||||
const container = document.getElementById('vote-chart-container');
|
||||
|
||||
console.log('Vote Data received:', data);
|
||||
|
||||
if (!data || data.length === 0) {
|
||||
renderNoData();
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the match date from the first item's keys
|
||||
const matchDate = '{{ _matchDate }}';
|
||||
const motmKey = 'motm_' + matchDate;
|
||||
const dotdKey = 'dotd_' + matchDate;
|
||||
|
||||
console.log('Using keys:', motmKey, dotdKey);
|
||||
|
||||
// Sort by MOTM votes (descending), then by DotD votes (ascending for better ranking)
|
||||
data.sort((a, b) => {
|
||||
const aMotm = a[motmKey] || 0;
|
||||
const bMotm = b[motmKey] || 0;
|
||||
const aDotd = a[dotdKey] || 0;
|
||||
const bDotd = b[dotdKey] || 0;
|
||||
|
||||
if (bMotm !== aMotm) {
|
||||
return bMotm - aMotm;
|
||||
}
|
||||
return aDotd - bDotd;
|
||||
});
|
||||
|
||||
// Find max values for scaling - each bar scales independently
|
||||
const maxMotm = Math.max(...data.map(p => p[motmKey] || 0));
|
||||
const maxDotd = Math.max(...data.map(p => p[dotdKey] || 0));
|
||||
|
||||
let html = '';
|
||||
|
||||
data.forEach((player, index) => {
|
||||
console.log('Processing vote player:', player);
|
||||
const ranking = index + 1;
|
||||
const rankingClass = ranking === 1 ? 'gold' : ranking === 2 ? 'silver' : ranking === 3 ? 'bronze' : '';
|
||||
|
||||
const motmVotes = player[motmKey] || 0;
|
||||
const dotdVotes = player[dotdKey] || 0;
|
||||
|
||||
html += '<div class="vote-player-card">';
|
||||
html += '<div class="d-flex align-items-center">';
|
||||
html += '<div class="vote-ranking ' + rankingClass + '">' + ranking + '</div>';
|
||||
html += '<div class="flex-grow-1">';
|
||||
html += '<div class="vote-player-name">' + (player.playerName || 'Unknown Player') + '</div>';
|
||||
html += '<div class="vote-stats">';
|
||||
html += '<div class="vote-stat">';
|
||||
html += '<div class="vote-stat-value text-success">' + motmVotes + '</div>';
|
||||
html += '<div class="vote-stat-label">MOTM</div>';
|
||||
html += '</div>';
|
||||
html += '<div class="vote-stat">';
|
||||
html += '<div class="vote-stat-value text-danger">' + dotdVotes + '</div>';
|
||||
html += '<div class="vote-stat-label">DotD</div>';
|
||||
html += '</div>';
|
||||
html += '<div class="vote-bar-container">';
|
||||
html += '<div class="vote-bar vote-bar-motm">';
|
||||
html += '<div class="vote-bar-fill" style="width: ' + (maxMotm > 0 ? (motmVotes / maxMotm * 100) : 0) + '%"></div>';
|
||||
html += '</div>';
|
||||
html += '<div class="vote-bar vote-bar-dotd">';
|
||||
html += '<div class="vote-bar-fill" style="width: ' + (maxDotd > 0 ? (dotdVotes / maxDotd * 100) : 0) + '%"></div>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
html += '</div>';
|
||||
});
|
||||
|
||||
container.innerHTML = html;
|
||||
|
||||
// Animate bars after a short delay
|
||||
setTimeout(() => {
|
||||
const bars = container.querySelectorAll('.vote-bar-fill');
|
||||
bars.forEach(bar => {
|
||||
const width = bar.style.width;
|
||||
bar.style.width = '0%';
|
||||
setTimeout(() => {
|
||||
bar.style.width = width;
|
||||
}, 100);
|
||||
});
|
||||
}, 200);
|
||||
}
|
||||
|
||||
function renderNoData() {
|
||||
const container = document.getElementById('vote-chart-container');
|
||||
container.innerHTML = '<div class="vote-no-data">' +
|
||||
'<i class="fas fa-chart-line"></i>' +
|
||||
'<h4>No Vote Data Available</h4>' +
|
||||
'<p>No votes have been cast for this match yet.</p>' +
|
||||
'<p class="text-muted">Votes will appear here once players start voting.</p>' +
|
||||
'</div>';
|
||||
}
|
||||
|
||||
// Test function to verify basic functionality
|
||||
function testVoteChart() {
|
||||
const testData = [
|
||||
{playerName: 'Test Player 1', motm_{{ _matchDate }}: 3, dotd_{{ _matchDate }}: 1},
|
||||
{playerName: 'Test Player 2', motm_{{ _matchDate }}: 2, dotd_{{ _matchDate }}: 2}
|
||||
];
|
||||
console.log('Testing with sample vote data:', testData);
|
||||
renderVoteChart(testData);
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
Loading…
Reference in New Issue
Block a user