Improved readability

parent 5b0da3e1
......@@ -2080,6 +2080,66 @@ class Migration_027_AddDefaultIntroTemplatesConfig(DatabaseMigration):
logger.error(f"Failed to remove default intro templates configuration: {e}")
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
MIGRATIONS: List[DatabaseMigration] = [
Migration_001_InitialSchema(),
......@@ -2109,6 +2169,7 @@ MIGRATIONS: List[DatabaseMigration] = [
Migration_025_AddResultOptionModel(),
Migration_026_AddExtractionStatsTable(),
Migration_027_AddDefaultIntroTemplatesConfig(),
Migration_028_AddFixtureRefreshIntervalConfig(),
]
......
......@@ -45,7 +45,7 @@
}
.fixtures-panel {
background: rgba(0, 123, 255, 0.40);
background: rgba(0, 123, 255, 0.85);
border-radius: 20px;
padding: 30px;
max-width: 90%;
......@@ -60,7 +60,7 @@
.fixtures-title {
color: white;
font-size: 28px;
font-size: 56px;
font-weight: bold;
text-align: center;
margin-bottom: 25px;
......@@ -71,7 +71,7 @@
width: 100%;
border-collapse: collapse;
color: white;
font-size: 16px;
font-size: 32px;
background: transparent;
}
......@@ -80,7 +80,7 @@
text-align: center;
background: rgba(255, 255, 255, 0.1);
font-weight: bold;
font-size: 11px;
font-size: 22px;
text-transform: uppercase;
letter-spacing: 1px;
border-radius: 8px;
......@@ -104,7 +104,7 @@
.next-match-info {
text-align: center;
color: rgba(255, 255, 255, 0.9);
font-size: 16px;
font-size: 32px;
margin-bottom: 10px;
font-weight: bold;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
......@@ -113,7 +113,7 @@
.countdown-timer {
text-align: center;
color: #ffeb3b;
font-size: 24px;
font-size: 48px;
font-weight: bold;
margin-bottom: 15px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
......@@ -146,12 +146,12 @@
}
.fighter-names {
font-size: 14px;
font-size: 28px;
color: #e6f3ff;
}
.venue-info {
font-size: 13px;
font-size: 26px;
color: #ccddff;
font-style: italic;
}
......@@ -179,14 +179,14 @@
.loading-message {
text-align: center;
color: white;
font-size: 18px;
font-size: 36px;
padding: 40px;
}
.no-matches {
text-align: center;
color: rgba(255, 255, 255, 0.8);
font-size: 16px;
font-size: 32px;
padding: 30px;
font-style: italic;
}
......@@ -194,7 +194,7 @@
.fixture-info {
text-align: center;
color: rgba(255, 255, 255, 0.9);
font-size: 14px;
font-size: 28px;
margin-bottom: 20px;
font-style: italic;
}
......@@ -219,11 +219,11 @@
}
.fixtures-title {
font-size: 24px;
font-size: 48px;
}
.fixtures-table {
font-size: 14px;
font-size: 28px;
}
.fixtures-table th,
......@@ -240,12 +240,12 @@
}
.fixtures-title {
font-size: 20px;
font-size: 40px;
margin-bottom: 15px;
}
.fixtures-table {
font-size: 12px;
font-size: 24px;
}
.fixtures-table th,
......@@ -255,7 +255,7 @@
.odds-value {
padding: 2px 4px;
font-size: 11px;
font-size: 22px;
}
}
......@@ -282,7 +282,7 @@
<body>
<div class="overlay-container">
<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="countdown-timer" id="countdownTimer" style="display: none;"></div>
<div class="loading-message" id="loadingMessage" style="display: none;">Loading fixture data...</div>
......@@ -376,8 +376,8 @@
fixturesData = data.matches;
renderFixtures();
} else {
console.log('🔍 DEBUG: No valid fixture data in WebChannel update, showing fallback');
showFallbackMatches();
console.log('🔍 DEBUG: No valid fixture data in WebChannel update, waiting for data');
// Don't show fallback data - wait for real data
}
}
......@@ -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
function renderFixtures() {
const loadingMessage = document.getElementById('loadingMessage');
......@@ -530,15 +617,23 @@
// Get all available outcomes from the matches
const allOutcomes = new Set();
fixturesData.forEach(match => {
if (match.outcomes && match.outcomes.length > 0) {
if (match.outcomes) {
if (Array.isArray(match.outcomes)) {
// Handle array format from API
match.outcomes.forEach(outcome => {
// Handle both API formats
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);
console.log(`Found outcome: ${outcomeName} for match ${match.id}`);
});
}
}
});
......@@ -576,20 +671,27 @@
// Create table body
tableBody.innerHTML = '';
fixturesData.forEach(match => {
fixturesData.slice(0, 5).forEach(match => {
const row = document.createElement('tr');
// Create outcomes map for quick lookup
const outcomeMap = {};
if (match.outcomes && match.outcomes.length > 0) {
if (match.outcomes) {
if (Array.isArray(match.outcomes)) {
// Handle array format from API
match.outcomes.forEach(outcome => {
// Handle both API formats
const outcomeName = outcome.outcome_name || outcome.column_name;
const outcomeValue = outcome.outcome_value || outcome.float_value;
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`);
......@@ -613,6 +715,9 @@
});
fixturesContent.style.display = 'block';
// Find next match and start countdown
findNextMatchAndStartCountdown();
}
// Show no matches message
......@@ -638,6 +743,20 @@
// Wait for WebChannel to provide data - no direct API fetching
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
setTimeout(function() {
console.log('🔍 DEBUG: Fallback - trying to trigger data load via WebChannel');
......@@ -655,15 +774,13 @@
}
}, 1000);
// Fallback timeout - if no data received after 10 seconds, show fallback
// Show no matches if no data after 3 seconds
setTimeout(() => {
if (!fixturesData || fixturesData.length === 0) {
console.log('🔍 DEBUG: No data received from WebChannel after timeout, showing fallback');
showFallbackMatches();
} else {
console.log('🔍 DEBUG: Data received from WebChannel, no fallback needed');
console.log('🔍 DEBUG: No data received after 3 seconds');
showNoMatches('Waiting for fixture data...');
}
}, 10000);
}, 3000);
});
// Qt WebChannel initialization (when available)
......
......@@ -244,8 +244,8 @@ let isInitialLoad = true;
document.addEventListener('DOMContentLoaded', function() {
loadFixtureDetails();
// Set up auto-refresh every 5 seconds
setInterval(loadFixtureDetails, 5000);
// Set up auto-refresh every 15 seconds
setInterval(loadFixtureDetails, 15000);
});
function loadFixtureDetails() {
......
......@@ -167,8 +167,8 @@ document.addEventListener('DOMContentLoaded', function() {
resetBtn.addEventListener('click', resetFixtures);
}
// Auto-refresh fixtures every 5 seconds
setInterval(loadFixtures, 5000);
// Auto-refresh fixtures every 15 seconds
setInterval(loadFixtures, 15000);
});
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