Improved readability

parent 5b0da3e1
...@@ -2080,6 +2080,66 @@ class Migration_027_AddDefaultIntroTemplatesConfig(DatabaseMigration): ...@@ -2080,6 +2080,66 @@ class Migration_027_AddDefaultIntroTemplatesConfig(DatabaseMigration):
logger.error(f"Failed to remove default intro templates configuration: {e}") logger.error(f"Failed to remove default intro templates configuration: {e}")
return False return False
class Migration_028_AddFixtureRefreshIntervalConfig(DatabaseMigration):
"""Add fixture refresh interval configuration for web dashboard"""
def __init__(self):
super().__init__("028", "Add fixture refresh interval configuration for web dashboard")
def up(self, db_manager) -> bool:
"""Add fixture refresh interval configuration to game_config table"""
try:
with db_manager.engine.connect() as conn:
# Check if fixture_refresh_interval already exists
result = conn.execute(text("""
SELECT COUNT(*) FROM game_config WHERE config_key = 'fixture_refresh_interval'
"""))
exists = result.scalar() > 0
if not exists:
conn.execute(text("""
INSERT INTO game_config
(config_key, config_value, value_type, description, is_system, created_at, updated_at)
VALUES (:config_key, :config_value, :value_type, :description, :is_system, datetime('now'), datetime('now'))
"""), {
'config_key': 'fixture_refresh_interval',
'config_value': '15',
'value_type': 'int',
'description': 'Auto-refresh interval in seconds for fixture pages in web dashboard',
'is_system': False
})
logger.info("Added fixture refresh interval configuration (15 seconds)")
else:
logger.info("Fixture refresh interval configuration already exists")
conn.commit()
logger.info("Fixture refresh interval configuration migration completed successfully")
return True
except Exception as e:
logger.error(f"Failed to add fixture refresh interval configuration: {e}")
return False
def down(self, db_manager) -> bool:
"""Remove fixture refresh interval configuration"""
try:
with db_manager.engine.connect() as conn:
# Remove the fixture refresh interval configuration
conn.execute(text("""
DELETE FROM game_config WHERE config_key = 'fixture_refresh_interval'
"""))
conn.commit()
logger.info("Fixture refresh interval configuration removed")
return True
except Exception as e:
logger.error(f"Failed to remove fixture refresh interval configuration: {e}")
return False
# Registry of all migrations in order # Registry of all migrations in order
MIGRATIONS: List[DatabaseMigration] = [ MIGRATIONS: List[DatabaseMigration] = [
Migration_001_InitialSchema(), Migration_001_InitialSchema(),
...@@ -2109,6 +2169,7 @@ MIGRATIONS: List[DatabaseMigration] = [ ...@@ -2109,6 +2169,7 @@ MIGRATIONS: List[DatabaseMigration] = [
Migration_025_AddResultOptionModel(), Migration_025_AddResultOptionModel(),
Migration_026_AddExtractionStatsTable(), Migration_026_AddExtractionStatsTable(),
Migration_027_AddDefaultIntroTemplatesConfig(), Migration_027_AddDefaultIntroTemplatesConfig(),
Migration_028_AddFixtureRefreshIntervalConfig(),
] ]
......
...@@ -45,7 +45,7 @@ ...@@ -45,7 +45,7 @@
} }
.fixtures-panel { .fixtures-panel {
background: rgba(0, 123, 255, 0.40); background: rgba(0, 123, 255, 0.85);
border-radius: 20px; border-radius: 20px;
padding: 30px; padding: 30px;
max-width: 90%; max-width: 90%;
...@@ -60,7 +60,7 @@ ...@@ -60,7 +60,7 @@
.fixtures-title { .fixtures-title {
color: white; color: white;
font-size: 28px; font-size: 56px;
font-weight: bold; font-weight: bold;
text-align: center; text-align: center;
margin-bottom: 25px; margin-bottom: 25px;
...@@ -71,7 +71,7 @@ ...@@ -71,7 +71,7 @@
width: 100%; width: 100%;
border-collapse: collapse; border-collapse: collapse;
color: white; color: white;
font-size: 16px; font-size: 32px;
background: transparent; background: transparent;
} }
...@@ -80,7 +80,7 @@ ...@@ -80,7 +80,7 @@
text-align: center; text-align: center;
background: rgba(255, 255, 255, 0.1); background: rgba(255, 255, 255, 0.1);
font-weight: bold; font-weight: bold;
font-size: 11px; font-size: 22px;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 1px; letter-spacing: 1px;
border-radius: 8px; border-radius: 8px;
...@@ -104,7 +104,7 @@ ...@@ -104,7 +104,7 @@
.next-match-info { .next-match-info {
text-align: center; text-align: center;
color: rgba(255, 255, 255, 0.9); color: rgba(255, 255, 255, 0.9);
font-size: 16px; font-size: 32px;
margin-bottom: 10px; margin-bottom: 10px;
font-weight: bold; font-weight: bold;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5); text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
...@@ -113,7 +113,7 @@ ...@@ -113,7 +113,7 @@
.countdown-timer { .countdown-timer {
text-align: center; text-align: center;
color: #ffeb3b; color: #ffeb3b;
font-size: 24px; font-size: 48px;
font-weight: bold; font-weight: bold;
margin-bottom: 15px; margin-bottom: 15px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8); text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
...@@ -146,12 +146,12 @@ ...@@ -146,12 +146,12 @@
} }
.fighter-names { .fighter-names {
font-size: 14px; font-size: 28px;
color: #e6f3ff; color: #e6f3ff;
} }
.venue-info { .venue-info {
font-size: 13px; font-size: 26px;
color: #ccddff; color: #ccddff;
font-style: italic; font-style: italic;
} }
...@@ -179,22 +179,22 @@ ...@@ -179,22 +179,22 @@
.loading-message { .loading-message {
text-align: center; text-align: center;
color: white; color: white;
font-size: 18px; font-size: 36px;
padding: 40px; padding: 40px;
} }
.no-matches { .no-matches {
text-align: center; text-align: center;
color: rgba(255, 255, 255, 0.8); color: rgba(255, 255, 255, 0.8);
font-size: 16px; font-size: 32px;
padding: 30px; padding: 30px;
font-style: italic; font-style: italic;
} }
.fixture-info { .fixture-info {
text-align: center; text-align: center;
color: rgba(255, 255, 255, 0.9); color: rgba(255, 255, 255, 0.9);
font-size: 14px; font-size: 28px;
margin-bottom: 20px; margin-bottom: 20px;
font-style: italic; font-style: italic;
} }
...@@ -219,11 +219,11 @@ ...@@ -219,11 +219,11 @@
} }
.fixtures-title { .fixtures-title {
font-size: 24px; font-size: 48px;
} }
.fixtures-table { .fixtures-table {
font-size: 14px; font-size: 28px;
} }
.fixtures-table th, .fixtures-table th,
...@@ -240,22 +240,22 @@ ...@@ -240,22 +240,22 @@
} }
.fixtures-title { .fixtures-title {
font-size: 20px; font-size: 40px;
margin-bottom: 15px; margin-bottom: 15px;
} }
.fixtures-table { .fixtures-table {
font-size: 12px; font-size: 24px;
} }
.fixtures-table th, .fixtures-table th,
.fixtures-table td { .fixtures-table td {
padding: 6px 4px; padding: 6px 4px;
} }
.odds-value { .odds-value {
padding: 2px 4px; padding: 2px 4px;
font-size: 11px; font-size: 22px;
} }
} }
...@@ -282,7 +282,7 @@ ...@@ -282,7 +282,7 @@
<body> <body>
<div class="overlay-container"> <div class="overlay-container">
<div class="fixtures-panel" id="fixturesPanel"> <div class="fixtures-panel" id="fixturesPanel">
<div class="fixtures-title">Today's matches</div> <div class="fixtures-title">Next 5 matches:</div>
<div class="next-match-info" id="nextMatchInfo" style="display: none;"></div> <div class="next-match-info" id="nextMatchInfo" style="display: none;"></div>
<div class="countdown-timer" id="countdownTimer" style="display: none;"></div> <div class="countdown-timer" id="countdownTimer" style="display: none;"></div>
<div class="loading-message" id="loadingMessage" style="display: none;">Loading fixture data...</div> <div class="loading-message" id="loadingMessage" style="display: none;">Loading fixture data...</div>
...@@ -376,8 +376,8 @@ ...@@ -376,8 +376,8 @@
fixturesData = data.matches; fixturesData = data.matches;
renderFixtures(); renderFixtures();
} else { } else {
console.log('🔍 DEBUG: No valid fixture data in WebChannel update, showing fallback'); console.log('🔍 DEBUG: No valid fixture data in WebChannel update, waiting for data');
showFallbackMatches(); // Don't show fallback data - wait for real data
} }
} }
...@@ -511,6 +511,93 @@ ...@@ -511,6 +511,93 @@
]; ];
} }
// Find next match and start countdown
function findNextMatchAndStartCountdown() {
if (!fixturesData || fixturesData.length === 0) {
return;
}
// Clear any existing countdown
if (countdownInterval) {
clearInterval(countdownInterval);
countdownInterval = null;
}
const now = new Date();
let nextMatch = null;
let earliestTime = null;
// Find the match with the earliest start time that hasn't started yet
for (const match of fixturesData) {
if (match.start_time) {
const startTime = new Date(match.start_time);
if (startTime > now && (!earliestTime || startTime < earliestTime)) {
earliestTime = startTime;
nextMatch = match;
}
}
}
if (nextMatch && earliestTime) {
nextMatchStartTime = earliestTime;
// Show next match info
const nextMatchInfo = document.getElementById('nextMatchInfo');
nextMatchInfo.textContent = `Next: ${nextMatch.fighter1_township || nextMatch.fighter1} vs ${nextMatch.fighter2_township || nextMatch.fighter2}`;
nextMatchInfo.style.display = 'block';
// Start countdown
updateCountdown();
countdownInterval = setInterval(updateCountdown, 1000);
} else {
// No upcoming matches, hide countdown
document.getElementById('nextMatchInfo').style.display = 'none';
document.getElementById('countdownTimer').style.display = 'none';
}
}
// Update countdown display
function updateCountdown() {
if (!nextMatchStartTime) {
return;
}
const now = new Date();
const timeDiff = nextMatchStartTime - now;
if (timeDiff <= 0) {
// Match has started
document.getElementById('countdownTimer').textContent = 'LIVE';
document.getElementById('countdownTimer').className = 'countdown-timer';
return;
}
const hours = Math.floor(timeDiff / (1000 * 60 * 60));
const minutes = Math.floor((timeDiff % (1000 * 60 * 60)) / (1000 * 60));
const seconds = Math.floor((timeDiff % (1000 * 60)) / 1000);
let timeString = '';
if (hours > 0) {
timeString = `${hours}:${minutes.toString().padStart(2, '0')}:${seconds.toString().padStart(2, '0')}`;
} else {
timeString = `${minutes}:${seconds.toString().padStart(2, '0')}`;
}
const countdownTimer = document.getElementById('countdownTimer');
countdownTimer.textContent = timeString;
// Add warning/urgent classes based on time remaining
if (timeDiff <= 60000) { // 1 minute
countdownTimer.className = 'countdown-timer urgent';
} else if (timeDiff <= 300000) { // 5 minutes
countdownTimer.className = 'countdown-timer warning';
} else {
countdownTimer.className = 'countdown-timer';
}
countdownTimer.style.display = 'block';
}
// Render the fixtures table // Render the fixtures table
function renderFixtures() { function renderFixtures() {
const loadingMessage = document.getElementById('loadingMessage'); const loadingMessage = document.getElementById('loadingMessage');
...@@ -518,30 +605,38 @@ ...@@ -518,30 +605,38 @@
const noMatches = document.getElementById('noMatches'); const noMatches = document.getElementById('noMatches');
const tableHeader = document.getElementById('tableHeader'); const tableHeader = document.getElementById('tableHeader');
const tableBody = document.getElementById('tableBody'); const tableBody = document.getElementById('tableBody');
loadingMessage.style.display = 'none'; loadingMessage.style.display = 'none';
noMatches.style.display = 'none'; noMatches.style.display = 'none';
if (!fixturesData || fixturesData.length === 0) { if (!fixturesData || fixturesData.length === 0) {
showNoMatches('No matches available for today'); showNoMatches('No matches available for today');
return; return;
} }
// Get all available outcomes from the matches // Get all available outcomes from the matches
const allOutcomes = new Set(); const allOutcomes = new Set();
fixturesData.forEach(match => { fixturesData.forEach(match => {
if (match.outcomes && match.outcomes.length > 0) { if (match.outcomes) {
match.outcomes.forEach(outcome => { if (Array.isArray(match.outcomes)) {
// Handle both API formats // Handle array format from API
const outcomeName = outcome.outcome_name || outcome.column_name; match.outcomes.forEach(outcome => {
if (outcomeName) { const outcomeName = outcome.outcome_name || outcome.column_name;
if (outcomeName) {
allOutcomes.add(outcomeName);
console.log(`Found outcome: ${outcomeName} for match ${match.id}`);
}
});
} else if (typeof match.outcomes === 'object') {
// Handle dict format from database
Object.keys(match.outcomes).forEach(outcomeName => {
allOutcomes.add(outcomeName); allOutcomes.add(outcomeName);
console.log(`Found outcome: ${outcomeName} for match ${match.id}`); console.log(`Found outcome: ${outcomeName} for match ${match.id}`);
} });
}); }
} }
}); });
console.log(`Total unique outcomes found: ${allOutcomes.size}`, Array.from(allOutcomes)); console.log(`Total unique outcomes found: ${allOutcomes.size}`, Array.from(allOutcomes));
// Sort outcomes: common ones first, then alphabetically // Sort outcomes: common ones first, then alphabetically
...@@ -576,20 +671,27 @@ ...@@ -576,20 +671,27 @@
// Create table body // Create table body
tableBody.innerHTML = ''; tableBody.innerHTML = '';
fixturesData.forEach(match => { fixturesData.slice(0, 5).forEach(match => {
const row = document.createElement('tr'); const row = document.createElement('tr');
// Create outcomes map for quick lookup // Create outcomes map for quick lookup
const outcomeMap = {}; const outcomeMap = {};
if (match.outcomes && match.outcomes.length > 0) { if (match.outcomes) {
match.outcomes.forEach(outcome => { if (Array.isArray(match.outcomes)) {
// Handle both API formats // Handle array format from API
const outcomeName = outcome.outcome_name || outcome.column_name; match.outcomes.forEach(outcome => {
const outcomeValue = outcome.outcome_value || outcome.float_value; const outcomeName = outcome.outcome_name || outcome.column_name;
if (outcomeName) { const outcomeValue = outcome.outcome_value || outcome.float_value;
outcomeMap[outcomeName] = outcomeValue; if (outcomeName) {
} outcomeMap[outcomeName] = outcomeValue;
}); }
});
} else if (typeof match.outcomes === 'object') {
// Handle dict format from database
Object.entries(match.outcomes).forEach(([key, value]) => {
outcomeMap[key] = value;
});
}
} }
console.log(`Match ${match.id || match.match_number}: Found ${Object.keys(outcomeMap).length} outcomes`); console.log(`Match ${match.id || match.match_number}: Found ${Object.keys(outcomeMap).length} outcomes`);
...@@ -613,6 +715,9 @@ ...@@ -613,6 +715,9 @@
}); });
fixturesContent.style.display = 'block'; fixturesContent.style.display = 'block';
// Find next match and start countdown
findNextMatchAndStartCountdown();
} }
// Show no matches message // Show no matches message
...@@ -638,6 +743,20 @@ ...@@ -638,6 +743,20 @@
// Wait for WebChannel to provide data - no direct API fetching // Wait for WebChannel to provide data - no direct API fetching
console.log('🔍 DEBUG: Waiting for Python side to provide fixture data via WebChannel'); console.log('🔍 DEBUG: Waiting for Python side to provide fixture data via WebChannel');
// Try to trigger data load via WebChannel immediately
if (typeof qt !== 'undefined' && qt.webChannelTransport) {
try {
new QWebChannel(qt.webChannelTransport, function(channel) {
if (channel.objects.overlay && channel.objects.overlay.trigger_fixtures_data_load) {
console.log('🔍 DEBUG: Calling trigger_fixtures_data_load immediately');
channel.objects.overlay.trigger_fixtures_data_load();
}
});
} catch (e) {
console.log('🔍 DEBUG: Immediate WebChannel trigger failed:', e);
}
}
// Fallback: Try to trigger data load via WebChannel after 1 second // Fallback: Try to trigger data load via WebChannel after 1 second
setTimeout(function() { setTimeout(function() {
console.log('🔍 DEBUG: Fallback - trying to trigger data load via WebChannel'); console.log('🔍 DEBUG: Fallback - trying to trigger data load via WebChannel');
...@@ -655,15 +774,13 @@ ...@@ -655,15 +774,13 @@
} }
}, 1000); }, 1000);
// Fallback timeout - if no data received after 10 seconds, show fallback // Show no matches if no data after 3 seconds
setTimeout(() => { setTimeout(() => {
if (!fixturesData || fixturesData.length === 0) { if (!fixturesData || fixturesData.length === 0) {
console.log('🔍 DEBUG: No data received from WebChannel after timeout, showing fallback'); console.log('🔍 DEBUG: No data received after 3 seconds');
showFallbackMatches(); showNoMatches('Waiting for fixture data...');
} else {
console.log('🔍 DEBUG: Data received from WebChannel, no fallback needed');
} }
}, 10000); }, 3000);
}); });
// Qt WebChannel initialization (when available) // Qt WebChannel initialization (when available)
......
...@@ -244,8 +244,8 @@ let isInitialLoad = true; ...@@ -244,8 +244,8 @@ let isInitialLoad = true;
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
loadFixtureDetails(); loadFixtureDetails();
// Set up auto-refresh every 5 seconds // Set up auto-refresh every 15 seconds
setInterval(loadFixtureDetails, 5000); setInterval(loadFixtureDetails, 15000);
}); });
function loadFixtureDetails() { function loadFixtureDetails() {
......
...@@ -167,8 +167,8 @@ document.addEventListener('DOMContentLoaded', function() { ...@@ -167,8 +167,8 @@ document.addEventListener('DOMContentLoaded', function() {
resetBtn.addEventListener('click', resetFixtures); resetBtn.addEventListener('click', resetFixtures);
} }
// Auto-refresh fixtures every 5 seconds // Auto-refresh fixtures every 15 seconds
setInterval(loadFixtures, 5000); setInterval(loadFixtures, 15000);
}); });
let isInitialLoad = true; let isInitialLoad = true;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment