Refactoring fixes and adding functionality back

This commit is contained in:
Jonny Ervine 2025-10-07 22:03:36 +08:00
parent d5350aa4cb
commit a6febae8a8
17 changed files with 458 additions and 298 deletions

View File

@ -261,9 +261,9 @@ class DatabaseConfigManager:
# Create sample fixtures (only if they don't exist)
fixtures_data = [
{'fixture_number': 1, 'date': datetime(2024, 1, 15), 'home_team': 'HKFC C', 'away_team': 'KCC A', 'venue': 'HKFC'},
{'fixture_number': 2, 'date': datetime(2024, 1, 22), 'home_team': 'USRC A', 'away_team': 'HKFC C', 'venue': 'USRC'},
{'fixture_number': 3, 'date': datetime(2024, 1, 29), 'home_team': 'HKFC C', 'away_team': 'Valley A', 'venue': 'HKFC'},
{'fixture_number': 1, 'date': datetime(2025, 1, 15), 'home_team': 'HKFC C', 'away_team': 'KCC A', 'venue': 'HKFC'},
{'fixture_number': 2, 'date': datetime(2025, 1, 22), 'home_team': 'USRC A', 'away_team': 'HKFC C', 'venue': 'USRC'},
{'fixture_number': 3, 'date': datetime(2025, 1, 29), 'home_team': 'HKFC C', 'away_team': 'Valley A', 'venue': 'HKFC'},
]
for fixture_data in fixtures_data:

View File

@ -269,8 +269,21 @@ def match_comments():
return render_template('error.html', message="Database not initialized. Please go to Database Setup to initialize the database.")
_oppo = row[0]['nextclub']
commentDate = row[0]['nextdate'].strftime('%Y-%m-%d')
_matchDate = row[0]['nextdate'].strftime('%Y_%m_%d')
# Handle case where nextdate is None - use most recent comment date
if row[0]['nextdate']:
commentDate = row[0]['nextdate'].strftime('%Y-%m-%d')
_matchDate = row[0]['nextdate'].strftime('%Y-%m-%d')
else:
# Get the most recent comment date
sql_recent = text("SELECT matchDate FROM _motmcomments ORDER BY matchDate DESC LIMIT 1")
recent_result = sql_read(sql_recent)
if recent_result:
commentDate = recent_result[0]['matchDate']
_matchDate = recent_result[0]['matchDate']
else:
commentDate = '2025-01-15' # Fallback
_matchDate = '2025-01-15'
# Get HKFC logo from clubs table using signed URLs (with authentication)
hkfcLogo = s3_asset_service.get_asset_url('images/hkfc_logo.png') # Default fallback
@ -376,7 +389,24 @@ def vote_thanks():
sql3 = text("INSERT INTO _motmcomments (matchDate, comment) VALUES (:match_date, :comment)")
sql_write(sql3, {'match_date': _matchDate, 'comment': _fixed_comments})
return render_template('vote_thanks.html')
# Get Simpsons monkeys image URL with fallback
try:
# First try to get from S3
simpsons_url = s3_asset_service.get_asset_url('images/simpsons-monkeys.jpg')
print(f"DEBUG: Simpsons image URL: {simpsons_url}")
# If S3 is disabled or URL is fallback, use static
if simpsons_url.startswith('/static/'):
print("DEBUG: Using fallback static URL")
else:
print("DEBUG: Using S3 URL")
except Exception as e:
print(f"DEBUG: Error getting Simpsons image: {e}")
# Fallback to static URL
simpsons_url = "/static/images/simpsons-monkeys.jpg"
return render_template('vote_thanks.html', simpsons_image_url=simpsons_url)
else:
return 'Ouch ... something went wrong here'
except Exception as e:
@ -513,7 +543,15 @@ def motm_admin():
form.nextOppoClub.choices = [(oppo['hockeyclub'], oppo['hockeyclub']) for oppo in clubs]
form.currMotM.choices = [(player['playernumber'], player['playerforenames'] + " " + player['playersurname']) for player in players]
form.currDotD.choices = [(player['playernumber'], player['playerforenames'] + " " + player['playersurname']) for player in players]
clubLogo = settings[0]['oppologo']
# Get the opposition logo using S3 service
clubLogo = s3_asset_service.get_asset_url('images/default_logo.png') # Default fallback
if settings and settings[0]['nextclub']:
nextClub = settings[0]['nextclub']
# Get the club logo from the clubs table
sql_club_logo = text("SELECT logo_url FROM clubs WHERE hockey_club = :club_name")
club_logo_result = sql_read(sql_club_logo, {'club_name': nextClub})
if club_logo_result and club_logo_result[0]['logo_url']:
clubLogo = s3_asset_service.get_logo_url(club_logo_result[0]['logo_url'], nextClub)
return render_template('motm_admin.html', form=form, nextOppoLogo=clubLogo)
@ -1451,6 +1489,11 @@ def get_next_fixture():
# Get opponent club information
opponent_club_info = get_opponent_club_info(fixture['opponent'])
# Get the opponent logo URL using S3 service
opponent_logo_url = s3_asset_service.get_asset_url('images/default_logo.png') # Default fallback
if opponent_club_info and opponent_club_info.get('logo_url'):
opponent_logo_url = s3_asset_service.get_logo_url(opponent_club_info['logo_url'], opponent_club_info['club_name'])
# Format the fixture data for JSON response
fixture_data = {
'success': True,
@ -1461,6 +1504,7 @@ def get_next_fixture():
'opponent': fixture['opponent'],
'opponent_club': get_opponent_club_name(fixture['opponent']),
'opponent_club_info': opponent_club_info,
'opponent_logo_url': opponent_logo_url,
'is_home': fixture['is_home'],
'home_team': fixture['home_team'],
'away_team': fixture['away_team'],
@ -1683,9 +1727,9 @@ def poty_results():
# Only include players with votes
if motm_total > 0 or dotd_total > 0:
results.append({
'playername': player['playername'],
'motmtotal': motm_total,
'dotdtotal': dotd_total
'playerName': player['playername'], # Fixed field name to match JavaScript
'motmTotal': motm_total, # Fixed field name to match JavaScript
'dotdTotal': dotd_total # Fixed field name to match JavaScript
})
print(f"Dynamic POTY Results: {results}")
@ -1703,7 +1747,7 @@ def voting_chart():
if date_result:
matchDate = str(date_result[0]['nextdate']).replace('-', '')
else:
matchDate = '20251012' # Default fallback
matchDate = '20251015' # Default fallback
return render_template('vote_chart.html', _matchDate=matchDate)

View File

@ -203,7 +203,13 @@ class S3AssetService:
region_name=config.get('aws_region', 'us-east-1'),
endpoint_url=endpoint_url,
use_ssl=use_ssl,
verify=True # Enable SSL certificate verification
verify=True, # Enable SSL certificate verification
config=boto3.session.Config(
s3={
'addressing_style': 'path'
},
signature_version='s3v4'
)
)
else:
# Create AWS S3 client

View File

@ -0,0 +1 @@
iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNkYPhfDwAChwGA60e6kgAAAABJRU5ErkJggg==

View File

@ -56,6 +56,15 @@
</div>
</a>
</div>
<div class="col-md-3 mb-3">
<a href="/admin/motm/manage" class="btn btn-outline-danger w-100">
<i class="fas fa-database me-2"></i>
<div class="text-start">
<div class="fw-bold">MOTM Management</div>
<small class="text-muted">Reset & drop columns</small>
</div>
</a>
</div>
<div class="col-md-3 mb-3">
<a href="/admin/profile" class="btn btn-outline-secondary w-100">
<i class="fas fa-user-cog me-2"></i>
@ -150,6 +159,76 @@
</div>
</div>
<!-- Analytics & Reports Section -->
<div class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-header bg-info text-white">
<h5 class="card-title mb-0">
<i class="fas fa-chart-bar me-2"></i>Analytics & Reports
</h5>
</div>
<div class="card-body">
<div class="row">
<div class="col-lg-4 mb-3">
<div class="card h-100 border-info">
<div class="card-header bg-light">
<h6 class="card-title mb-0">
<i class="fas fa-poll text-info me-2"></i>Vote Charts
</h6>
</div>
<div class="card-body">
<p class="card-text">View current vote counts and MOTM/DotD results for the current match.</p>
<div class="d-grid gap-2">
<a href="/admin/voting" class="btn btn-info">
<i class="fas fa-chart-pie me-2"></i>View Vote Charts
</a>
</div>
</div>
</div>
</div>
<div class="col-lg-4 mb-3">
<div class="card h-100 border-success">
<div class="card-header bg-light">
<h6 class="card-title mb-0">
<i class="fas fa-trophy text-success me-2"></i>Player of the Year
</h6>
</div>
<div class="card-body">
<p class="card-text">View Player of the Year standings and cumulative MOTM votes.</p>
<div class="d-grid gap-2">
<a href="/admin/poty" class="btn btn-success">
<i class="fas fa-medal me-2"></i>View POTY Chart
</a>
</div>
</div>
</div>
</div>
<div class="col-lg-4 mb-3">
<div class="card h-100 border-warning">
<div class="card-header bg-light">
<h6 class="card-title mb-0">
<i class="fas fa-futbol text-warning me-2"></i>Goals & Assists
</h6>
</div>
<div class="card-body">
<p class="card-text">Manage goals and assists statistics for matches.</p>
<div class="d-grid gap-2">
<a href="/admin/stats" class="btn btn-warning">
<i class="fas fa-plus me-2"></i>Add Stats
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- System Management Section -->
<div class="row mb-4">
<div class="col-12">

View File

@ -278,3 +278,4 @@
</div>
</div>
{% endblock %}

View File

@ -306,7 +306,7 @@
<div class="col-md-6 text-md-end">
<p class="mb-0">
<i class="fas fa-calendar-alt me-1"></i>
{% if current_year %}{{ current_year }}{% else %}2024{% endif %}
{% if current_year %}{{ current_year }}{% else %}2025{% endif %}
</p>
</div>
</div>

View File

@ -33,3 +33,4 @@
</div>
</div>
{% endblock %}

View File

@ -227,7 +227,7 @@
</div>
<div class="col-md-6">
<h6><i class="fas fa-calendar-alt me-2"></i>Current Season</h6>
<p class="text-muted">2024-2025 Hockey Season</p>
<p class="text-muted">2025-2026 Hockey Season</p>
</div>
</div>
</div>

View File

@ -227,7 +227,7 @@
</div>
<div class="col-md-6">
<h6><i class="fas fa-calendar-alt me-2"></i>Current Season</h6>
<p class="text-muted">2024-2025 Hockey Season</p>
<p class="text-muted">2025-2026 Hockey Season</p>
</div>
</div>
</div>
@ -235,3 +235,4 @@
</div>
</div>
{% endblock %}

View File

@ -74,3 +74,4 @@
</div>
</div>
{% endblock %}

View File

@ -1,176 +1,212 @@
<html>
<head>
<title>HKFC Men's C Team - MotM and DotD vote admin</title>
<link rel="stylesheet" media="screen" href ="/static/css/bootstrap.min.css">
<link rel="stylesheet" href="/static/css/bootstrap-theme.min.css">
<meta name="viewport" content = "width=device-width, initial-scale=1.0">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="/static/js/bootstrap.min.js"></script>
</head>
<h2>HKFC Men's C Team MotM and DotD online vote admin page</h2>
<div style="margin-bottom: 15px;">
<a href="/admin" class="btn btn-default btn-sm">
<span class="glyphicon glyphicon-arrow-left"></span> Back to Admin Dashboard
</a>
{% extends "base.html" %}
{% block title %}MOTM Management - HKFC Men's C Team 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>
MOTM Management
</h1>
<p class="lead text-muted">Manage Man of the Match and Dick of the Day settings</p>
<a href="/admin" class="btn btn-outline-primary">
<i class="fas fa-arrow-left me-2"></i>Back to Admin Dashboard
</a>
</div>
</div>
</div>
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for message in messages %}
<div class="alert alert-warning alert-dismissible" role="alert">
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
{{ message }}
</div>
{% endfor %}
{% endif %}
{% endwith %}
<body onload="myFunction()">
<dl>
<p>
</div>
<!-- Match Configuration Card -->
<div class="row mb-4">
<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-calendar-alt me-2"></i>Next Match Configuration
</h5>
</div>
<div class="card-body">
{{ form.csrf_token }}
<b>HKFC C Next Opponent:</b>
<br/>
<div class="row">
<div class="col-xs-12">
<form class="col-sm-6" method="post" action="/admin/motm">
<!-- Load Next Fixture Button -->
<div class="row">
<div class="col-sm-12">
<div class="alert alert-info" style="margin-bottom: 15px;">
<button type="button" class="btn btn-info btn-sm" id="loadFixtureBtn" onclick="loadNextFixture()">
<span class="glyphicon glyphicon-download-alt"></span> Load Next HKFC C Fixture
<form method="post" action="/admin/motm">
<!-- Load Next Fixture Section -->
<div class="row mb-4">
<div class="col-12">
<div class="alert alert-info">
<div class="d-flex align-items-center justify-content-between flex-wrap">
<div class="d-flex align-items-center">
<button type="button" class="btn btn-info me-3" id="loadFixtureBtn" onclick="loadNextFixture()">
<i class="fas fa-download me-2"></i>Load Next HKFC C Fixture
</button>
<a href="https://hockey.org.hk/MenFixture.asp" target="_blank" class="btn btn-default btn-sm" style="margin-left: 5px;">
<span class="glyphicon glyphicon-new-window"></span> View HK Hockey Fixtures
<a href="https://hockey.org.hk/MenFixture.asp" target="_blank" class="btn btn-outline-info">
<i class="fas fa-external-link-alt me-2"></i>View HK Hockey Fixtures
</a>
<span id="fixtureStatus" style="margin-left: 10px;"></span>
<div id="fixtureInfo" style="margin-top: 10px; display: none;"></div>
</div>
<span id="fixtureStatus" class="ms-3"></span>
</div>
<div id="fixtureInfo" class="mt-3" style="display: none;"></div>
</div>
<div class = "row">
<div class = "col-sm-6">
<div class="input-group">
<span class="input-group-addon" id="basic-addon1">Date:</span>
{{ form.nextMatchDate(class_="form-control", id="nextMatchDate") }}
</div>
</div>
</div>
</br>
<div class = "row">
<div class = "col-sm-9">
<div class="input-group">
<span class="input-group-addon" id="basic-addon1">Opposition</span>
{{ form.nextOppoTeam(class_="form-control", id="nextOppoTeam") }}
</div>
</div>
</div>
<div class = "row">
<div class = "col-sm-6">
<div class="input-group">
<span class="input-group-addon" id="basic-addon1">Current Man of the Match:</span>
{{ form.currMotM(class_="form-control") }}
</div>
</div>
<div class = "col-sm-6">
<div class="input-group">
<span class="input-group-addon" id="basic-addon1">Current Dick of the Day:</span>
{{ form.currDotD(class_="form-control") }}
</div>
</div>
</div>
{% if not form.currMotM.choices or form.currMotM.choices|length == 0 %}
<div class="row">
<div class="col-sm-12">
<div class="alert alert-warning" style="margin-top: 10px;">
<small><strong>Note:</strong> No players available for previous MOTM/DotD. This is normal if you haven't set up a match squad yet. You can still save the match details.</small>
</div>
</div>
</div>
{% endif %}
<p>
{{ form.saveButton(class_="btn btn-success") }}
{{ form.activateButton(class_="btn btn-primary") }}
<a class="btn btn-danger" href="/" role="button">Cancel</a>
</p>
</form>
<div class="col-sm-4">
<img src="{{ nextOppoLogo }}" height="90" id="nextOppoLogo"/>
</div>
</div>
</div>
</p>
</dl>
<!-- Match Details Form -->
<div class="row mb-4">
<div class="col-md-6 mb-3">
<label for="nextMatchDate" class="form-label">
<i class="fas fa-calendar me-2"></i>Match Date
</label>
{{ form.nextMatchDate(class_="form-control", **{"id": "nextMatchDate"}) }}
</div>
<div class="col-md-6 mb-3">
<label for="nextOppoTeam" class="form-label">
<i class="fas fa-users me-2"></i>Opposition Team
</label>
{{ form.nextOppoTeam(class_="form-control", **{"id": "nextOppoTeam"}) }}
</div>
</div>
<!-- Current Winners Section -->
<div class="row mb-4">
<div class="col-md-6 mb-3">
<label for="currMotM" class="form-label">
<i class="fas fa-trophy me-2 text-warning"></i>Current Man of the Match
</label>
{{ form.currMotM(class_="form-select") }}
</div>
<div class="col-md-6 mb-3">
<label for="currDotD" class="form-label">
<i class="fas fa-user-times me-2 text-danger"></i>Current Dick of the Day
</label>
{{ form.currDotD(class_="form-select") }}
</div>
</div>
<!-- Warning Message -->
{% if not form.currMotM.choices or form.currMotM.choices|length == 0 %}
<div class="row mb-4">
<div class="col-12">
<div class="alert alert-warning">
<i class="fas fa-exclamation-triangle me-2"></i>
<strong>Note:</strong> No players available for previous MOTM/DotD. This is normal if you haven't set up a match squad yet. You can still save the match details.
</div>
</div>
</div>
{% endif %}
<!-- Action Buttons -->
<div class="row">
<div class="col-12">
<div class="d-flex gap-2 flex-wrap">
{{ form.saveButton(class_="btn btn-success") }}
{{ form.activateButton(class_="btn btn-primary") }}
<a class="btn btn-outline-secondary" href="/admin" role="button">
<i class="fas fa-times me-2"></i>Cancel
</a>
</div>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
<script>
function loadNextFixture() {
// Show loading status
var statusElement = document.getElementById('fixtureStatus');
var infoElement = document.getElementById('fixtureInfo');
var loadBtn = document.getElementById('loadFixtureBtn');
statusElement.innerHTML = '<span class="text-info">Loading...</span>';
loadBtn.disabled = true;
// Fetch the next fixture from the API
fetch('/admin/api/next-fixture')
.then(response => response.json())
.then(data => {
if (data.success) {
// Update the form fields
document.getElementById('nextMatchDate').value = data.date;
document.getElementById('nextOppoTeam').value = data.opponent;
// Show fixture information
let clubInfo = '';
if (data.opponent_club_info) {
const club = data.opponent_club_info;
const confidence = club.match_result ? club.match_result.confidence : 'unknown';
const matchType = club.match_result ? club.match_result.match_type : 'unknown';
clubInfo = '<br><small class="text-muted">';
clubInfo += 'Club: ' + club.club_name;
if (club.logo_url) {
clubInfo += ' | <a href="' + club.logo_url + '" target="_blank">Logo</a>';
}
clubInfo += ' | Match: ' + matchType + ' (' + confidence + ')';
clubInfo += '</small>';
}
infoElement.innerHTML = '<strong>Next Match:</strong> ' +
data.date_formatted + ' vs ' + data.opponent +
' (' + (data.is_home ? 'Home' : 'Away') + ' - ' + data.venue + ')' +
'<br><small>Division: ' + data.division + ' | Time: ' + data.time + '</small>' +
clubInfo;
infoElement.style.display = 'block';
statusElement.innerHTML = '<span class="text-success">✓ Fixture loaded!</span>';
// Clear status message after 3 seconds
setTimeout(function() {
statusElement.innerHTML = '';
}, 3000);
} else {
statusElement.innerHTML = '<span class="text-danger">✗ ' + data.message + '</span>';
infoElement.style.display = 'none';
<!-- Opposition Logo Card -->
<div class="row mb-4">
<div class="col-12">
<div class="card">
<div class="card-header bg-info text-white">
<h5 class="card-title mb-0">
<i class="fas fa-image me-2"></i>Opposition Team Logo
</h5>
</div>
<div class="card-body text-center">
<img src="{{ nextOppoLogo }}" height="120" id="nextOppoLogo" class="img-fluid rounded" alt="Opposition Team Logo"/>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_scripts %}
<script>
function loadNextFixture() {
// Show loading status
const statusElement = document.getElementById('fixtureStatus');
const infoElement = document.getElementById('fixtureInfo');
const loadBtn = document.getElementById('loadFixtureBtn');
statusElement.innerHTML = '<span class="text-info"><i class="fas fa-spinner fa-spin me-2"></i>Loading...</span>';
loadBtn.disabled = true;
// Fetch the next fixture from the API
fetch('/admin/api/next-fixture')
.then(response => response.json())
.then(data => {
if (data.success) {
// Update the form fields
document.getElementById('nextMatchDate').value = data.date;
document.getElementById('nextOppoTeam').value = data.opponent;
// Update the opponent logo if provided
if (data.opponent_logo_url) {
const logoElement = document.getElementById('nextOppoLogo');
if (logoElement) {
logoElement.src = data.opponent_logo_url;
}
loadBtn.disabled = false;
})
.catch(error => {
console.error('Error:', error);
statusElement.innerHTML = '<span class="text-danger">✗ Error loading fixture</span>';
infoElement.style.display = 'none';
loadBtn.disabled = false;
});
}
// Auto-load fixture on page load
function myFunction() {
// Optional: Auto-load the next fixture when the page loads
// Uncomment the next line if you want this behavior
// loadNextFixture();
}
</script>
</body>
</html>
}
// Show fixture information
let clubInfo = '';
if (data.opponent_club_info) {
const club = data.opponent_club_info;
const confidence = club.match_result ? club.match_result.confidence : 'unknown';
const matchType = club.match_result ? club.match_result.match_type : 'unknown';
clubInfo = '<br><small class="text-muted">';
clubInfo += 'Club: ' + club.club_name;
if (club.logo_url) {
clubInfo += ' | <a href="' + club.logo_url + '" target="_blank">Logo</a>';
}
clubInfo += ' | Match: ' + matchType + ' (' + confidence + ')';
clubInfo += '</small>';
}
infoElement.innerHTML = '<strong>Next Match:</strong> ' +
data.date_formatted + ' vs ' + data.opponent +
' (' + (data.is_home ? 'Home' : 'Away') + ' - ' + data.venue + ')' +
'<br><small>Division: ' + data.division + ' | Time: ' + data.time + '</small>' +
clubInfo;
infoElement.style.display = 'block';
statusElement.innerHTML = '<span class="text-success"><i class="fas fa-check me-2"></i>Fixture loaded!</span>';
// Clear status message after 3 seconds
setTimeout(function() {
statusElement.innerHTML = '';
}, 3000);
} else {
statusElement.innerHTML = '<span class="text-danger"><i class="fas fa-times me-2"></i>' + data.message + '</span>';
infoElement.style.display = 'none';
}
loadBtn.disabled = false;
})
.catch(error => {
console.error('Error:', error);
statusElement.innerHTML = '<span class="text-danger"><i class="fas fa-exclamation-triangle me-2"></i>Error loading fixture</span>';
infoElement.style.display = 'none';
loadBtn.disabled = false;
});
}
// Auto-load fixture on page load (optional)
document.addEventListener('DOMContentLoaded', function() {
// Uncomment the next line if you want to auto-load the fixture when the page loads
// loadNextFixture();
});
</script>
{% endblock %}

View File

@ -38,6 +38,7 @@
</div>
<!-- Random Comment -->
{% if comment and comment != "No comments added yet" %}
<div class="row mb-4">
<div class="col-12">
<div class="card">
@ -47,28 +48,27 @@
</h5>
</div>
<div class="card-body">
{% for item in comment %}
<blockquote class="blockquote text-center">
<p class="mb-0">
<i class="fas fa-quote-left text-muted me-2"></i>
<em>{{ item.comment }}</em>
<i class="fas fa-quote-right text-muted ms-2"></i>
</p>
</blockquote>
{% endfor %}
<blockquote class="blockquote text-center">
<p class="mb-0">
<i class="fas fa-quote-left text-muted me-2"></i>
<em>{{ comment }}</em>
<i class="fas fa-quote-right text-muted ms-2"></i>
</p>
</blockquote>
</div>
</div>
</div>
</div>
{% endif %}
<!-- Voting Forms -->
<div class="row">
<div class="col-lg-6 mb-4">
<!-- Man of the Match -->
<div class="col-lg-8 mx-auto mb-4">
<!-- Vote for MOTM and DotD -->
<div class="card h-100">
<div class="card-header bg-success text-white">
<h5 class="card-title mb-0">
<i class="fas fa-trophy me-2"></i>Man of the Match
<i class="fas fa-vote-yea me-2"></i>Vote for MOTM and DotD
</h5>
</div>
<div class="card-body">
@ -79,7 +79,7 @@
<div class="mb-3">
<label for="motmSelect" class="form-label">Select Player:</label>
<select class="form-select" id="motmSelect" name="motmSelect" required>
<select class="form-select" id="motmSelect" name="motmVote" required>
<option value="">Choose a player...</option>
{% for player in data %}
<option value="{{ player.playernumber }}">{{ player.playerforenames }} {{ player.playersurname }}
@ -89,45 +89,26 @@
</select>
</div>
<div class="mb-3">
<label for="dotdSelect" class="form-label">Dick of the Day:</label>
<select class="form-select" id="dotdSelect" name="dotdVote" required>
<option value="">Choose a player...</option>
{% for player in data %}
<option value="{{ player.playernumber }}">{{ player.playerforenames }} {{ player.playersurname }}
{% if player.playernickname %}"{{ player.playernickname }}"{% endif %}
</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
<label for="motmComment" class="form-label">Optional Comment:</label>
<textarea class="form-control" id="motmComment" name="motmComment" rows="3" placeholder="Share your thoughts about the match..."></textarea>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-success btn-lg">
<i class="fas fa-vote-yea me-2"></i>Submit MOTM Vote
</button>
</div>
</form>
</div>
</div>
</div>
<div class="col-lg-6 mb-4">
<!-- Dick of the Day -->
<div class="card h-100">
<div class="card-header bg-warning text-dark">
<h5 class="card-title mb-0">
<i class="fas fa-award me-2"></i>Dick of the Day
</h5>
</div>
<div class="card-body">
<form method="post" action="/motm/dotd-thanks" id="dotdForm">
{{ form.csrf_token }}
<input type="hidden" id="matchNumber" name="matchNumber" value="{{ matchNumber }}">
<input type="hidden" id="oppo" name="oppo" value="{{ oppo }}">
<div class="mb-3">
<label for="dotdSelect" class="form-label">Select Player:</label>
<select class="form-select" id="dotdSelect" name="dotdSelect" required>
<option value="">Choose a player...</option>
{% for player in data %}
<option value="{{ player.playernumber }}">{{ player.playerforenames }} {{ player.playersurname }}
{% if player.playernickname %}"{{ player.playernickname }}"{% endif %}
</option>
{% endfor %}
</select>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-warning btn-lg">
<i class="fas fa-vote-yea me-2"></i>Submit DotD Vote
<i class="fas fa-vote-yea me-2"></i>Submit Votes
</button>
</div>
</form>
@ -198,18 +179,27 @@
<script>
$(document).ready(function() {
// Form validation
$('#motmForm, #dotdForm').on('submit', function(e) {
const selectElement = $(this).find('select');
if (!selectElement.val()) {
$('#motmForm').on('submit', function(e) {
const motmSelect = $('#motmSelect');
const dotdSelect = $('#dotdSelect');
if (!motmSelect.val()) {
e.preventDefault();
alert('Please select a player before submitting your vote.');
selectElement.focus();
alert('Please select a player for Man of the Match.');
motmSelect.focus();
return false;
}
if (!dotdSelect.val()) {
e.preventDefault();
alert('Please select a player for Dick of the Day.');
dotdSelect.focus();
return false;
}
});
// Add loading state to buttons
$('#motmForm, #dotdForm').on('submit', function() {
// Add loading state to button
$('#motmForm').on('submit', function() {
const button = $(this).find('button[type="submit"]');
const originalText = button.html();
button.html('<span class="loading-spinner"></span> Submitting...').prop('disabled', true);

View File

@ -38,6 +38,7 @@
</div>
<!-- Random Comment -->
{% if comment and comment != "No comments added yet" %}
<div class="row mb-4">
<div class="col-12">
<div class="card">
@ -47,28 +48,27 @@
</h5>
</div>
<div class="card-body">
{% for item in comment %}
<blockquote class="blockquote text-center">
<p class="mb-0">
<i class="fas fa-quote-left text-muted me-2"></i>
<em>{{ item.comment }}</em>
<i class="fas fa-quote-right text-muted ms-2"></i>
</p>
</blockquote>
{% endfor %}
<blockquote class="blockquote text-center">
<p class="mb-0">
<i class="fas fa-quote-left text-muted me-2"></i>
<em>{{ comment }}</em>
<i class="fas fa-quote-right text-muted ms-2"></i>
</p>
</blockquote>
</div>
</div>
</div>
</div>
{% endif %}
<!-- Voting Forms -->
<div class="row">
<div class="col-lg-6 mb-4">
<!-- Man of the Match -->
<div class="col-lg-8 mx-auto mb-4">
<!-- Vote for MOTM and DotD -->
<div class="card h-100">
<div class="card-header bg-success text-white">
<h5 class="card-title mb-0">
<i class="fas fa-trophy me-2"></i>Man of the Match
<i class="fas fa-vote-yea me-2"></i>Vote for MOTM and DotD
</h5>
</div>
<div class="card-body">
@ -79,7 +79,7 @@
<div class="mb-3">
<label for="motmSelect" class="form-label">Select Player:</label>
<select class="form-select" id="motmSelect" name="motmSelect" required>
<select class="form-select" id="motmSelect" name="motmVote" required>
<option value="">Choose a player...</option>
{% for player in data %}
<option value="{{ player.playernumber }}">{{ player.playerforenames }} {{ player.playersurname }}
@ -89,45 +89,26 @@
</select>
</div>
<div class="mb-3">
<label for="dotdSelect" class="form-label">Dick of the Day:</label>
<select class="form-select" id="dotdSelect" name="dotdVote" required>
<option value="">Choose a player...</option>
{% for player in data %}
<option value="{{ player.playernumber }}">{{ player.playerforenames }} {{ player.playersurname }}
{% if player.playernickname %}"{{ player.playernickname }}"{% endif %}
</option>
{% endfor %}
</select>
</div>
<div class="mb-3">
<label for="motmComment" class="form-label">Optional Comment:</label>
<textarea class="form-control" id="motmComment" name="motmComment" rows="3" placeholder="Share your thoughts about the match..."></textarea>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-success btn-lg">
<i class="fas fa-vote-yea me-2"></i>Submit MOTM Vote
</button>
</div>
</form>
</div>
</div>
</div>
<div class="col-lg-6 mb-4">
<!-- Dick of the Day -->
<div class="card h-100">
<div class="card-header bg-warning text-dark">
<h5 class="card-title mb-0">
<i class="fas fa-award me-2"></i>Dick of the Day
</h5>
</div>
<div class="card-body">
<form method="post" action="/motm/dotd-thanks" id="dotdForm">
{{ form.csrf_token }}
<input type="hidden" id="matchNumber" name="matchNumber" value="{{ matchNumber }}">
<input type="hidden" id="oppo" name="oppo" value="{{ oppo }}">
<div class="mb-3">
<label for="dotdSelect" class="form-label">Select Player:</label>
<select class="form-select" id="dotdSelect" name="dotdSelect" required>
<option value="">Choose a player...</option>
{% for player in data %}
<option value="{{ player.playernumber }}">{{ player.playerforenames }} {{ player.playersurname }}
{% if player.playernickname %}"{{ player.playernickname }}"{% endif %}
</option>
{% endfor %}
</select>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-warning btn-lg">
<i class="fas fa-vote-yea me-2"></i>Submit DotD Vote
<i class="fas fa-vote-yea me-2"></i>Submit Votes
</button>
</div>
</form>
@ -198,18 +179,27 @@
<script>
$(document).ready(function() {
// Form validation
$('#motmForm, #dotdForm').on('submit', function(e) {
const selectElement = $(this).find('select');
if (!selectElement.val()) {
$('#motmForm').on('submit', function(e) {
const motmSelect = $('#motmSelect');
const dotdSelect = $('#dotdSelect');
if (!motmSelect.val()) {
e.preventDefault();
alert('Please select a player before submitting your vote.');
selectElement.focus();
alert('Please select a player for Man of the Match.');
motmSelect.focus();
return false;
}
if (!dotdSelect.val()) {
e.preventDefault();
alert('Please select a player for Dick of the Day.');
dotdSelect.focus();
return false;
}
});
// Add loading state to buttons
$('#motmForm, #dotdForm').on('submit', function() {
// Add loading state to button
$('#motmForm').on('submit', function() {
const button = $(this).find('button[type="submit"]');
const originalText = button.html();
button.html('<span class="loading-spinner"></span> Submitting...').prop('disabled', true);
@ -217,3 +207,4 @@ $(document).ready(function() {
});
</script>
{% endblock %}

View File

@ -36,6 +36,12 @@ google.charts.load('current', {
// 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,

View File

@ -23,10 +23,11 @@
</div>
<div class="mb-4">
<img src="http://icarus.ipa.champion:9000/hockey-app/assets/simpsons-monkeys.jpg"
<img src="{{ simpsons_image_url }}"
alt="Counting votes"
class="img-fluid rounded"
style="max-height: 300px;">
style="max-height: 300px;"
onerror="this.src='/static/images/simpsons-monkeys.jpg';">
</div>
<div class="d-grid gap-2 d-md-flex justify-content-md-center">

View File

@ -23,10 +23,11 @@
</div>
<div class="mb-4">
<img src="http://icarus.ipa.champion:9000/hockey-app/assets/simpsons-monkeys.jpg"
<img src="{{ simpsons_image_url }}"
alt="Counting votes"
class="img-fluid rounded"
style="max-height: 300px;">
style="max-height: 300px;"
onerror="this.src='/static/images/simpsons-monkeys.jpg';">
</div>
<div class="d-grid gap-2 d-md-flex justify-content-md-center">
@ -42,3 +43,4 @@
</div>
</div>
{% endblock %}