Fixture template works now

parent 0632ac59
...@@ -595,7 +595,9 @@ class MbetterClientApplication: ...@@ -595,7 +595,9 @@ class MbetterClientApplication:
def _process_core_message(self, message: Message): def _process_core_message(self, message: Message):
"""Process messages received by the core component""" """Process messages received by the core component"""
try: try:
logger.debug(f"Core processing message: {message}") # Suppress debug logging for VIDEO_PROGRESS messages to reduce log noise
if message.type != MessageType.VIDEO_PROGRESS:
logger.debug(f"Core processing message: {message}")
if message.type == MessageType.SYSTEM_STATUS: if message.type == MessageType.SYSTEM_STATUS:
self._handle_system_status(message) self._handle_system_status(message)
...@@ -608,7 +610,9 @@ class MbetterClientApplication: ...@@ -608,7 +610,9 @@ class MbetterClientApplication:
elif message.type == MessageType.SYSTEM_SHUTDOWN: elif message.type == MessageType.SYSTEM_SHUTDOWN:
self._handle_shutdown_message(message) self._handle_shutdown_message(message)
else: else:
logger.debug(f"Unhandled message type in core: {message.type}") # Suppress debug logging for VIDEO_PROGRESS messages to reduce log noise
if message.type != MessageType.VIDEO_PROGRESS:
logger.debug(f"Unhandled message type in core: {message.type}")
except Exception as e: except Exception as e:
logger.error(f"Failed to process core message: {e}") logger.error(f"Failed to process core message: {e}")
......
...@@ -47,11 +47,12 @@ logger = logging.getLogger(__name__) ...@@ -47,11 +47,12 @@ logger = logging.getLogger(__name__)
class OverlayWebChannel(QObject): class OverlayWebChannel(QObject):
"""QObject for WebChannel communication with overlay HTML/JS""" """QObject for WebChannel communication with overlay HTML/JS"""
# Signals to send data to JavaScript # Signals to send data to JavaScript
dataUpdated = pyqtSignal(dict) dataUpdated = pyqtSignal(dict)
positionChanged = pyqtSignal(float, float) # position, duration in seconds positionChanged = pyqtSignal(float, float) # position, duration in seconds
videoInfoChanged = pyqtSignal(dict) videoInfoChanged = pyqtSignal(dict)
fixtureDataUpdated = pyqtSignal(dict) # Signal for fixture data updates
def __init__(self): def __init__(self):
super().__init__() super().__init__()
...@@ -178,6 +179,43 @@ class OverlayWebChannel(QObject): ...@@ -178,6 +179,43 @@ class OverlayWebChannel(QObject):
else: else:
logger.debug("Video info contained only null/undefined values, skipping") logger.debug("Video info contained only null/undefined values, skipping")
def send_fixture_data_update(self, data: Dict[str, Any]):
"""Send fixture data update to JavaScript (thread-safe)"""
# Validate data before emitting
if not data:
logger.warning("send_fixture_data_update called with null/empty data, skipping")
return
logger.debug(f"OverlayWebChannel sending fixture data: {data}")
self.fixtureDataUpdated.emit(data)
@pyqtSlot()
def trigger_fixtures_data_load(self):
"""Slot to manually trigger fixtures data loading from JavaScript"""
try:
logger.info("Manual trigger of fixtures data load from JavaScript")
# Find the PlayerWindow and trigger fixtures data load
# This method is called from JavaScript, so we need to find the PlayerWindow
overlay_view = None
# Try to find the overlay view that contains this channel
# We need to traverse up to find the PlayerWindow
current_obj = self.parent()
while current_obj:
if hasattr(current_obj, '_find_player_window'):
player_window = current_obj._find_player_window()
if player_window and hasattr(player_window, '_send_fixtures_to_overlay'):
logger.debug("Found PlayerWindow, triggering fixtures data load")
player_window._send_fixtures_to_overlay()
return
current_obj = current_obj.parent()
logger.warning("Could not find PlayerWindow to trigger fixtures data load")
except Exception as e:
logger.error(f"Failed to trigger fixtures data load from JavaScript: {e}")
class VideoProcessingWorker(QRunnable): class VideoProcessingWorker(QRunnable):
"""Background worker for video processing tasks""" """Background worker for video processing tasks"""
...@@ -419,6 +457,11 @@ class OverlayWebView(QWebEngineView): ...@@ -419,6 +457,11 @@ class OverlayWebView(QWebEngineView):
from PyQt6.QtCore import QTimer from PyQt6.QtCore import QTimer
QTimer.singleShot(100, lambda: self._ensure_overlay_visibility_post_load(was_visible)) QTimer.singleShot(100, lambda: self._ensure_overlay_visibility_post_load(was_visible))
# If fixtures template was loaded, trigger fixtures data loading
if template_name == "fixtures.html" or template_name == "fixtures":
logger.info("Fixtures template loaded, scheduling fixtures data load")
QTimer.singleShot(500, lambda: self._trigger_fixtures_data_load())
if self.debug_overlay: if self.debug_overlay:
logger.debug(f"GREEN SCREEN DEBUG: Template load initiated - {template_path}") logger.debug(f"GREEN SCREEN DEBUG: Template load initiated - {template_path}")
logger.info(f"Loaded template: {template_path} (source: {template_source})") logger.info(f"Loaded template: {template_path} (source: {template_source})")
...@@ -619,6 +662,51 @@ class OverlayWebView(QWebEngineView): ...@@ -619,6 +662,51 @@ class OverlayWebView(QWebEngineView):
if self.overlay_channel: if self.overlay_channel:
self.overlay_channel.send_video_info(info) self.overlay_channel.send_video_info(info)
def _trigger_fixtures_data_load(self):
"""Trigger fixtures data loading when fixtures template is loaded"""
try:
logger.info("Triggering fixtures data load for fixtures template")
# Find the PlayerWindow by traversing up the parent hierarchy
player_window = self._find_player_window()
if player_window and hasattr(player_window, '_send_fixtures_to_overlay'):
logger.debug("Calling PlayerWindow's _send_fixtures_to_overlay method")
player_window._send_fixtures_to_overlay()
else:
logger.warning("Could not find PlayerWindow with _send_fixtures_to_overlay method")
except Exception as e:
logger.error(f"Failed to trigger fixtures data load: {e}")
def _find_player_window(self):
"""Find the PlayerWindow by traversing up the parent hierarchy"""
try:
current = self.parent()
while current:
if hasattr(current, '_send_fixtures_to_overlay'):
logger.debug("Found PlayerWindow in parent hierarchy")
return current
current = current.parent()
logger.debug("PlayerWindow not found in parent hierarchy")
return None
except Exception as e:
logger.error(f"Error finding PlayerWindow: {e}")
return None
def update_fixture_data(self, fixture_data: Dict[str, Any]):
"""Update fixture data"""
if self.overlay_channel:
self.overlay_channel.send_fixture_data(fixture_data)
def _on_fixture_data_updated(self, fixture_data: Dict[str, Any]):
"""Handle fixture data update signal and call JavaScript function"""
try:
# Call the JavaScript updateFixtureData function
js_code = f"updateFixtureData({json.dumps(fixture_data)});"
self.page().runJavaScript(js_code)
logger.debug("Called JavaScript updateFixtureData function")
except Exception as e:
logger.error(f"Failed to call JavaScript updateFixtureData: {e}")
class NativeOverlayWidget(QWidget): class NativeOverlayWidget(QWidget):
"""Native Qt overlay widget - no WebEngine to prevent freezing""" """Native Qt overlay widget - no WebEngine to prevent freezing"""
...@@ -1271,6 +1359,11 @@ class PlayerWindow(QMainWindow): ...@@ -1271,6 +1359,11 @@ class PlayerWindow(QMainWindow):
self.overlay_timer.timeout.connect(self.update_overlay_periodically) self.overlay_timer.timeout.connect(self.update_overlay_periodically)
self.overlay_timer.start(1000) # Update every second self.overlay_timer.start(1000) # Update every second
# Fixtures data refresh timer
self.fixtures_timer = QTimer()
self.fixtures_timer.timeout.connect(self._refresh_fixtures_data)
self.fixtures_timer.start(30000) # Refresh fixtures every 30 seconds
# Template rotation timer (initially disabled) # Template rotation timer (initially disabled)
self.template_rotation_timer = QTimer() self.template_rotation_timer = QTimer()
self.template_rotation_timer.timeout.connect(self._rotate_template) self.template_rotation_timer.timeout.connect(self._rotate_template)
...@@ -1744,9 +1837,89 @@ class PlayerWindow(QMainWindow): ...@@ -1744,9 +1837,89 @@ class PlayerWindow(QMainWindow):
} }
if time_data and current_time: # Ensure we have valid data if time_data and current_time: # Ensure we have valid data
self._update_overlay_safe(overlay_view, time_data) self._update_overlay_safe(overlay_view, time_data)
except Exception as e: except Exception as e:
logger.error(f"Periodic overlay update failed: {e}") logger.error(f"Periodic overlay update failed: {e}")
def _refresh_fixtures_data(self):
"""Refresh fixtures data and send to overlay (called by timer)"""
try:
logger.debug("Refreshing fixtures data...")
self._send_fixtures_to_overlay()
except Exception as e:
logger.error(f"Failed to refresh fixtures data: {e}")
def _send_fixtures_to_overlay(self):
"""Send fixtures data to the overlay via WebChannel"""
try:
# Fetch fixtures data
fixtures_data = self._fetch_fixtures_data()
# Send data to overlay
if hasattr(self, 'window_overlay') and self.window_overlay:
if fixtures_data:
logger.info(f"Sending fixtures data to overlay: {len(fixtures_data.get('matches', []))} matches")
# Send via WebChannel signal - primary method
if hasattr(self.window_overlay, 'overlay_channel') and self.window_overlay.overlay_channel:
self.window_overlay.overlay_channel.send_fixture_data_update(fixtures_data)
logger.debug("Sent fixtures data via WebChannel signal")
# Also try calling JavaScript functions directly as fallback
try:
if hasattr(self.window_overlay, 'page'):
page = self.window_overlay.page()
if page:
# Try updateFixtureData function
page.runJavaScript(f"updateFixtureData({json.dumps(fixtures_data)});")
# Also try updateFixtures function for compatibility
page.runJavaScript(f"updateFixtures({json.dumps(fixtures_data)});")
logger.debug("Sent fixtures data via JavaScript function calls")
except Exception as js_error:
logger.debug(f"JavaScript call failed: {js_error}")
else:
logger.warning("No fixtures data available to send to overlay")
# Send empty data to trigger fallback
empty_data = {'matches': []}
if hasattr(self.window_overlay, 'overlay_channel') and self.window_overlay.overlay_channel:
self.window_overlay.overlay_channel.send_fixture_data_update(empty_data)
else:
logger.warning("No overlay available to send fixtures data")
except Exception as e:
logger.error(f"Failed to send fixtures data to overlay: {e}")
def _fetch_fixtures_data(self):
"""Fetch fixtures data from the web API"""
try:
import requests
from datetime import datetime, date
base_url = self._get_web_server_base_url()
api_url = f"{base_url}/api/cashier/pending-matches"
logger.info(f"Fetching fixtures data from: {api_url}")
# Make API request
response = requests.get(api_url, timeout=10)
if response.status_code == 200:
data = response.json()
if data.get('success') and data.get('matches'):
matches = data['matches']
logger.info(f"Successfully fetched {len(matches)} matches from API")
return {'matches': matches}
else:
logger.warning(f"API returned success=false or no matches: {data}")
return None
else:
logger.error(f"API request failed with status {response.status_code}: {response.text}")
return None
except Exception as e:
logger.error(f"Failed to fetch fixtures data: {e}")
return None
def mouseMoveEvent(self, event): def mouseMoveEvent(self, event):
"""Show controls on mouse movement""" """Show controls on mouse movement"""
...@@ -2019,6 +2192,10 @@ class QtVideoPlayer(QObject): ...@@ -2019,6 +2192,10 @@ class QtVideoPlayer(QObject):
self.window: Optional[PlayerWindow] = None self.window: Optional[PlayerWindow] = None
self.mutex = QMutex() self.mutex = QMutex()
# Progress update throttling (2 per second = 500ms minimum interval)
self.last_progress_update_time = 0.0
self.progress_update_interval = 0.5 # 500ms
# Set web dashboard URL for API calls # Set web dashboard URL for API calls
self.web_dashboard_url = "http://localhost:5000" # Default web dashboard URL self.web_dashboard_url = "http://localhost:5000" # Default web dashboard URL
...@@ -2038,22 +2215,103 @@ class QtVideoPlayer(QObject): ...@@ -2038,22 +2215,103 @@ class QtVideoPlayer(QObject):
# Default web server configuration - matches main.py defaults # Default web server configuration - matches main.py defaults
host = "127.0.0.1" # Default host host = "127.0.0.1" # Default host
port = 5001 # Default port port = 5001 # Default port
# Try to get web server settings if available # Try to get web server settings if available
if hasattr(self, '_message_bus') and self._message_bus: if hasattr(self, '_message_bus') and self._message_bus:
# Check if we can get web server info from message bus or settings # Check if we can get web server info from message bus or settings
# For now, use defaults since we don't have direct access to web settings # For now, use defaults since we don't have direct access to web settings
pass pass
# Construct base URL # Construct base URL
base_url = f"http://{host}:{port}" base_url = f"http://{host}:{port}"
logger.debug(f"Web server base URL determined as: {base_url}") logger.debug(f"Web server base URL determined as: {base_url}")
return base_url return base_url
except Exception as e: except Exception as e:
logger.error(f"Failed to determine web server base URL: {e}") logger.error(f"Failed to determine web server base URL: {e}")
# Return default fallback # Return default fallback
return "http://127.0.0.1:5001" return "http://127.0.0.1:5001"
def _fetch_fixtures_data(self) -> Optional[Dict[str, Any]]:
"""Fetch fixtures data from the web API"""
try:
import requests
from datetime import datetime, date
base_url = self._get_web_server_base_url()
api_url = f"{base_url}/api/cashier/pending-matches"
logger.info(f"Fetching fixtures data from: {api_url}")
# Make API request
response = requests.get(api_url, timeout=10)
if response.status_code == 200:
data = response.json()
if data.get('success') and data.get('matches'):
matches = data['matches']
logger.info(f"Successfully fetched {len(matches)} matches from API")
return {'matches': matches}
else:
logger.warning(f"API returned success=false or no matches: {data}")
return None
else:
logger.error(f"API request failed with status {response.status_code}: {response.text}")
return None
except Exception as e:
logger.error(f"Failed to fetch fixtures data: {e}")
return None
def _refresh_fixtures_data(self):
"""Refresh fixtures data and send to overlay (called by timer)"""
try:
logger.debug("Refreshing fixtures data...")
self._send_fixtures_to_overlay()
except Exception as e:
logger.error(f"Failed to refresh fixtures data: {e}")
def _send_fixtures_to_overlay(self, fixtures_data: Optional[Dict[str, Any]] = None):
"""Send fixtures data to the overlay via WebChannel"""
try:
# If no data provided, fetch it
if fixtures_data is None:
fixtures_data = self._fetch_fixtures_data()
# Send data to overlay
if hasattr(self, 'window_overlay') and self.window_overlay:
if fixtures_data:
logger.info(f"Sending fixtures data to overlay: {len(fixtures_data.get('matches', []))} matches")
# Send via WebChannel signal - primary method
if hasattr(self.window_overlay, 'overlay_channel') and self.window_overlay.overlay_channel:
self.window_overlay.overlay_channel.send_fixture_data_update(fixtures_data)
logger.debug("Sent fixtures data via WebChannel signal")
# Also try calling JavaScript functions directly as fallback
try:
if hasattr(self.window_overlay, 'page'):
page = self.window_overlay.page()
if page:
# Try updateFixtureData function
page.runJavaScript(f"updateFixtureData({json.dumps(fixtures_data)});")
# Also try updateFixtures function for compatibility
page.runJavaScript(f"updateFixtures({json.dumps(fixtures_data)});")
logger.debug("Sent fixtures data via JavaScript function calls")
except Exception as js_error:
logger.debug(f"JavaScript call failed: {js_error}")
else:
logger.warning("No fixtures data available to send to overlay")
# Send empty data to trigger fallback
empty_data = {'matches': []}
if hasattr(self.window_overlay, 'overlay_channel') and self.window_overlay.overlay_channel:
self.window_overlay.overlay_channel.send_fixture_data_update(empty_data)
else:
logger.warning("No overlay available to send fixtures data")
except Exception as e:
logger.error(f"Failed to send fixtures data to overlay: {e}")
def initialize(self) -> bool: def initialize(self) -> bool:
"""Initialize PyQt6 application and components""" """Initialize PyQt6 application and components"""
...@@ -2253,6 +2511,12 @@ class QtVideoPlayer(QObject): ...@@ -2253,6 +2511,12 @@ class QtVideoPlayer(QObject):
'showStats': False, 'showStats': False,
'webServerBaseUrl': self._get_web_server_base_url() 'webServerBaseUrl': self._get_web_server_base_url()
} }
# Fetch and include fixture data if available
fixture_data = self._fetch_fixture_data()
if fixture_data:
default_data['fixtures'] = fixture_data.get('matches', [])
logger.info(f"Included {len(default_data['fixtures'])} fixtures in default overlay")
if not self.window: if not self.window:
logger.debug("Window not ready for overlay loading") logger.debug("Window not ready for overlay loading")
...@@ -2630,21 +2894,25 @@ class QtVideoPlayer(QObject): ...@@ -2630,21 +2894,25 @@ class QtVideoPlayer(QObject):
logger.error(f"Failed to handle video loaded event: {e}") logger.error(f"Failed to handle video loaded event: {e}")
def _send_progress_update(self): def _send_progress_update(self):
"""Send video progress update""" """Send video progress update (throttled to 2 per second)"""
try: try:
if self.window and self.window.media_player.duration() > 0: # Throttle progress updates to 2 per second (500ms minimum interval)
position = self.window.media_player.position() current_time = time.time()
duration = self.window.media_player.duration() if current_time - self.last_progress_update_time >= self.progress_update_interval:
percentage = (position / duration) * 100 if duration > 0 else 0 if self.window and self.window.media_player.duration() > 0:
position = self.window.media_player.position()
progress_message = MessageBuilder.video_progress( duration = self.window.media_player.duration()
sender=self.name, percentage = (position / duration) * 100 if duration > 0 else 0
position=position / 1000.0, # Convert to seconds
duration=duration / 1000.0, # Convert to seconds progress_message = MessageBuilder.video_progress(
percentage=percentage sender=self.name,
) position=position / 1000.0, # Convert to seconds
self.message_bus.publish(progress_message, broadcast=True) duration=duration / 1000.0, # Convert to seconds
percentage=percentage
)
self.message_bus.publish(progress_message, broadcast=True)
self.last_progress_update_time = current_time
except Exception as e: except Exception as e:
logger.error(f"Failed to send progress update: {e}") logger.error(f"Failed to send progress update: {e}")
...@@ -2764,16 +3032,16 @@ class QtVideoPlayer(QObject): ...@@ -2764,16 +3032,16 @@ class QtVideoPlayer(QObject):
template_data = message.data.get("template_data", {}) template_data = message.data.get("template_data", {})
reload_template = template_data.get("reload_template", False) reload_template = template_data.get("reload_template", False)
load_specific_template = template_data.get("load_specific_template", "") load_specific_template = template_data.get("load_specific_template", "")
if self.debug_overlay: if self.debug_overlay:
logger.debug(f"GREEN SCREEN DEBUG: Template change message received") logger.debug(f"GREEN SCREEN DEBUG: Template change message received")
logger.debug(f"GREEN SCREEN DEBUG: Template name: {template_name}") logger.debug(f"GREEN SCREEN DEBUG: Template name: {template_name}")
logger.debug(f"GREEN SCREEN DEBUG: Reload template: {reload_template}") logger.debug(f"GREEN SCREEN DEBUG: Reload template: {reload_template}")
logger.debug(f"GREEN SCREEN DEBUG: Load specific template: {load_specific_template}") logger.debug(f"GREEN SCREEN DEBUG: Load specific template: {load_specific_template}")
if self.window and hasattr(self.window, 'window_overlay'): if self.window and hasattr(self.window, 'window_overlay'):
overlay_view = self.window.window_overlay overlay_view = self.window.window_overlay
# Check video player state before template change # Check video player state before template change
if hasattr(self.window, 'media_player') and self.debug_overlay: if hasattr(self.window, 'media_player') and self.debug_overlay:
video_state = self.window.media_player.playbackState() video_state = self.window.media_player.playbackState()
...@@ -2785,7 +3053,7 @@ class QtVideoPlayer(QObject): ...@@ -2785,7 +3053,7 @@ class QtVideoPlayer(QObject):
if hasattr(self.window, 'overlay_window') and self.debug_overlay: if hasattr(self.window, 'overlay_window') and self.debug_overlay:
logger.debug(f"GREEN SCREEN DEBUG: Overlay window geometry: {self.window.overlay_window.geometry()}") logger.debug(f"GREEN SCREEN DEBUG: Overlay window geometry: {self.window.overlay_window.geometry()}")
logger.debug(f"GREEN SCREEN DEBUG: Overlay window visible: {self.window.overlay_window.isVisible()}") logger.debug(f"GREEN SCREEN DEBUG: Overlay window visible: {self.window.overlay_window.isVisible()}")
# CRITICAL FIX: Protect video context during template changes # CRITICAL FIX: Protect video context during template changes
video_widget = None video_widget = None
if hasattr(self.window, 'video_widget') and hasattr(self.window.video_widget, 'get_video_widget'): if hasattr(self.window, 'video_widget') and hasattr(self.window.video_widget, 'get_video_widget'):
...@@ -2799,6 +3067,19 @@ class QtVideoPlayer(QObject): ...@@ -2799,6 +3067,19 @@ class QtVideoPlayer(QObject):
logger.debug(f"GREEN SCREEN DEBUG: About to load specific template: {load_specific_template}") logger.debug(f"GREEN SCREEN DEBUG: About to load specific template: {load_specific_template}")
logger.debug(f"GREEN SCREEN FIX: Protecting video rendering during template load") logger.debug(f"GREEN SCREEN FIX: Protecting video rendering during template load")
overlay_view.load_template(load_specific_template) overlay_view.load_template(load_specific_template)
# If loading fixtures template, fetch and send fixture data
if load_specific_template == "fixtures.html":
logger.info("Loading fixtures template, fetching fixture data...")
fixture_data = self._fetch_fixture_data()
if fixture_data:
# Send fixture data to the overlay
fixture_payload = {'fixtures': fixture_data.get('matches', [])}
self.window.update_fixture_data(fixture_payload)
logger.info(f"Sent {len(fixture_payload['fixtures'])} fixtures to fixtures overlay")
else:
logger.warning("No fixture data available for fixtures template")
if self.debug_overlay: if self.debug_overlay:
logger.debug(f"GREEN SCREEN DEBUG: Specific template load initiated") logger.debug(f"GREEN SCREEN DEBUG: Specific template load initiated")
# Otherwise reload current template if requested and using WebEngine overlay # Otherwise reload current template if requested and using WebEngine overlay
...@@ -2809,7 +3090,7 @@ class QtVideoPlayer(QObject): ...@@ -2809,7 +3090,7 @@ class QtVideoPlayer(QObject):
overlay_view.reload_current_template() overlay_view.reload_current_template()
if self.debug_overlay: if self.debug_overlay:
logger.debug(f"GREEN SCREEN DEBUG: Current template reload initiated") logger.debug(f"GREEN SCREEN DEBUG: Current template reload initiated")
# CRITICAL FIX: Force video widget refresh after template change # CRITICAL FIX: Force video widget refresh after template change
if video_widget and self.debug_overlay: if video_widget and self.debug_overlay:
logger.debug(f"GREEN SCREEN FIX: Forcing video widget refresh after template change") logger.debug(f"GREEN SCREEN FIX: Forcing video widget refresh after template change")
......
...@@ -76,11 +76,11 @@ ...@@ -76,11 +76,11 @@
} }
.fixtures-table th { .fixtures-table th {
padding: 15px 10px; padding: 8px 4px;
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: 14px; font-size: 11px;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 1px; letter-spacing: 1px;
border-radius: 8px; border-radius: 8px;
...@@ -89,7 +89,7 @@ ...@@ -89,7 +89,7 @@
} }
.fixtures-table td { .fixtures-table td {
padding: 12px 10px; padding: 8px 6px;
text-align: center; text-align: center;
background: rgba(255, 255, 255, 0.05); background: rgba(255, 255, 255, 0.05);
border-radius: 6px; border-radius: 6px;
...@@ -100,6 +100,45 @@ ...@@ -100,6 +100,45 @@
.fixtures-table tbody tr:hover td { .fixtures-table tbody tr:hover td {
background: rgba(255, 255, 255, 0.15); background: rgba(255, 255, 255, 0.15);
} }
.next-match-info {
text-align: center;
color: rgba(255, 255, 255, 0.9);
font-size: 16px;
margin-bottom: 10px;
font-weight: bold;
text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
}
.countdown-timer {
text-align: center;
color: #ffeb3b;
font-size: 24px;
font-weight: bold;
margin-bottom: 15px;
text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
background: rgba(0, 0, 0, 0.3);
padding: 8px 16px;
border-radius: 10px;
border: 2px solid rgba(255, 235, 59, 0.3);
}
.countdown-timer.warning {
color: #ff9800;
border-color: rgba(255, 152, 0, 0.5);
}
.countdown-timer.urgent {
color: #f44336;
border-color: rgba(244, 67, 54, 0.5);
animation: pulse 1s infinite;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
.match-info { .match-info {
font-weight: bold; font-weight: bold;
...@@ -244,6 +283,8 @@ ...@@ -244,6 +283,8 @@
<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">Today's 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> <div class="loading-message" id="loadingMessage" style="display: none;">Loading fixture data...</div>
<div id="fixturesContent" style="display: none;"> <div id="fixturesContent" style="display: none;">
<table class="fixtures-table" id="fixturesTable"> <table class="fixtures-table" id="fixturesTable">
...@@ -275,108 +316,109 @@ ...@@ -275,108 +316,109 @@
let overlayData = {}; let overlayData = {};
let fixturesData = null; let fixturesData = null;
let outcomesData = null; let outcomesData = null;
let countdownInterval = null;
let nextMatchStartTime = null;
// Web server configuration - will be set via WebChannel // Web server configuration - will be set via WebChannel
let webServerBaseUrl = 'http://127.0.0.1:5001'; // Default fallback let webServerBaseUrl = 'http://127.0.0.1:5001'; // Default fallback
// Debug logging function that sends messages to Qt application logs
function debugLog(message, level = 'info') {
try {
// Try to send to Qt WebChannel if available
if (typeof qt !== 'undefined' && qt.webChannelTransport) {
// Send debug message to Qt application
if (window.sendDebugMessage) {
window.sendDebugMessage(`[FIXTURES] ${message}`);
}
}
} catch (e) {
// Fallback to console if WebChannel not available - use original console to avoid recursion
originalConsoleLog(`[FIXTURES FALLBACK] ${message}`);
}
// Always log to console as well for browser debugging - use original to avoid recursion
originalConsoleLog(`🔍 DEBUG: ${message}`);
}
// Store original console.log before overriding
const originalConsoleLog = console.log;
// Override console.log to redirect all console.log calls to Qt application logs
console.log = function(...args) {
// Convert arguments to string message
const message = args.map(arg =>
typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
).join(' ');
// Send to Qt application logs directly (avoid calling debugLog to prevent recursion)
try {
if (typeof qt !== 'undefined' && qt.webChannelTransport) {
if (window.sendDebugMessage) {
window.sendDebugMessage(`[FIXTURES] ${message}`);
}
}
} catch (e) {
// Fallback handled below
}
// Always call original console.log for browser debugging
originalConsoleLog.apply(console, args);
};
// Function to receive fixture data from Qt WebChannel
function updateFixtureData(data) {
console.log('🔍 DEBUG: Received fixture data from WebChannel:', data);
if (data && data.fixtures) {
fixturesData = data.fixtures;
renderFixtures();
} else if (data && data.matches) {
fixturesData = data.matches;
renderFixtures();
} else {
console.log('🔍 DEBUG: No valid fixture data in WebChannel update, showing fallback');
showFallbackMatches();
}
}
// Function to update overlay data (called by Qt WebChannel) // Function to update overlay data (called by Qt WebChannel)
function updateOverlayData(data) { function updateOverlayData(data) {
console.log('Received overlay data:', data); console.log('Received overlay data:', data);
overlayData = data || {}; overlayData = data || {};
// Update web server base URL if provided // Update web server base URL if provided
if (data && data.webServerBaseUrl) { if (data && data.webServerBaseUrl) {
webServerBaseUrl = data.webServerBaseUrl; webServerBaseUrl = data.webServerBaseUrl;
console.log('Updated web server base URL:', webServerBaseUrl); console.log('Updated web server base URL:', webServerBaseUrl);
} }
// Check if we have fixtures data // Check if we have fixtures data from WebChannel
if (data && data.fixtures) { if (data && data.fixtures) {
console.log('🔍 DEBUG: Received fixtures data from WebChannel');
fixturesData = data.fixtures; fixturesData = data.fixtures;
renderFixtures(); renderFixtures();
} else if (data && data.matches) {
console.log('🔍 DEBUG: Received matches data from WebChannel');
fixturesData = data.matches;
renderFixtures();
} else { } else {
// Fetch fixtures data from API console.log('🔍 DEBUG: No fixtures data in WebChannel update, will wait for updateFixtureData call');
fetchFixturesData(); // Don't fetch from API - wait for Python side to provide data via updateFixtureData
} }
} }
// Fetch fixtures data from the API
async function fetchFixturesData() { // Function to receive fixture data from Qt WebChannel (called by Python) - new method
try { function updateFixtures(data) {
console.log('Fetching fixtures data from API...'); console.log('🔍 DEBUG: Received fixtures data from WebChannel via updateFixtures:', data);
updateFixtureData(data);
// Try multiple API endpoints with different authentication levels
const apiEndpoints = [
`${webServerBaseUrl}/api/cashier/pending-matches`,
`${webServerBaseUrl}/api/fixtures`,
`${webServerBaseUrl}/api/status` // Fallback to basic status endpoint
];
let apiData = null;
let usedEndpoint = null;
for (const endpoint of apiEndpoints) {
try {
console.log(`Trying API endpoint: ${endpoint}`);
const response = await fetch(endpoint, {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include' // Include cookies for authentication
});
if (response.ok) {
const data = await response.json();
console.log(`API Response from ${endpoint}:`, data);
if (data.success) {
apiData = data;
usedEndpoint = endpoint;
break;
}
} else {
console.warn(`API endpoint ${endpoint} returned status ${response.status}`);
}
} catch (endpointError) {
console.warn(`Failed to fetch from ${endpoint}:`, endpointError);
continue;
}
}
if (apiData && apiData.matches && apiData.matches.length > 0) {
console.log(`Found ${apiData.matches.length} matches from ${usedEndpoint}`);
fixturesData = apiData.matches;
renderFixtures();
return Promise.resolve();
} else if (apiData && apiData.fixtures && apiData.fixtures.length > 0) {
// Handle fixtures endpoint format
console.log(`Found ${apiData.fixtures.length} fixtures from ${usedEndpoint}`);
// Convert fixtures to matches format
fixturesData = [];
apiData.fixtures.forEach(fixture => {
if (fixture.matches) {
fixturesData.push(...fixture.matches);
}
});
if (fixturesData.length > 0) {
renderFixtures();
return Promise.resolve();
}
}
// If we reach here, no valid data was found
console.log('No fixture data available from any API endpoint, will show fallback');
return Promise.reject('No API data available');
} catch (error) {
console.error('Error fetching fixtures data:', error);
return Promise.reject(error);
}
} }
// Show fallback sample matches when API is not available // Show fallback sample matches when API is not available
function showFallbackMatches() { function showFallbackMatches() {
console.log('Showing fallback sample matches'); console.log('🔍 DEBUG: Showing fallback sample matches - API FAILED');
console.log('🔍 DEBUG: This indicates the API endpoints are not working properly');
fixturesData = [ fixturesData = [
{ {
id: 1, id: 1,
...@@ -407,6 +449,7 @@ ...@@ -407,6 +449,7 @@
] ]
} }
]; ];
console.log('🔍 DEBUG: Fallback data loaded, rendering fixtures');
renderFixtures(); renderFixtures();
} }
...@@ -583,54 +626,102 @@ ...@@ -583,54 +626,102 @@
// Initialize when DOM is loaded // Initialize when DOM is loaded
document.addEventListener('DOMContentLoaded', function() { document.addEventListener('DOMContentLoaded', function() {
console.log('Fixtures overlay initialized - attempting to fetch real data'); console.log('🔍 DEBUG: Fixtures overlay initialized - waiting for WebChannel data');
console.log('🔍 DEBUG: DOM ready, WebChannel will provide fixture data');
// Show loading message initially // Show loading message initially
document.getElementById('fixturesContent').style.display = 'none'; document.getElementById('fixturesContent').style.display = 'none';
document.getElementById('noMatches').style.display = 'none'; document.getElementById('noMatches').style.display = 'none';
document.getElementById('loadingMessage').style.display = 'block'; document.getElementById('loadingMessage').style.display = 'block';
document.getElementById('loadingMessage').textContent = 'Loading live fixture data...'; document.getElementById('loadingMessage').textContent = 'Loading live fixture data...';
// Start fetching real data immediately // Wait for WebChannel to provide data - no direct API fetching
fetchFixturesData().then(() => { console.log('🔍 DEBUG: Waiting for Python side to provide fixture data via WebChannel');
// If API fails completely, show fallback data after a short delay
setTimeout(() => { // Fallback: Try to trigger data load via WebChannel after 1 second
if (!fixturesData || fixturesData.length === 0) { setTimeout(function() {
console.log('No data loaded after API attempts, forcing fallback display'); console.log('🔍 DEBUG: Fallback - trying to trigger data load via WebChannel');
showFallbackMatches(); 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 from fallback timer');
channel.objects.overlay.trigger_fixtures_data_load();
}
});
} catch (e) {
console.log('🔍 DEBUG: Fallback WebChannel trigger failed:', e);
} }
}, 2000); }
}).catch(() => { }, 1000);
console.log('API fetch failed, showing fallback data');
showFallbackMatches(); // Fallback timeout - if no data received after 10 seconds, show fallback
}); setTimeout(() => {
if (!fixturesData || fixturesData.length === 0) {
// Refresh data every 30 seconds console.log('🔍 DEBUG: No data received from WebChannel after timeout, showing fallback');
setInterval(function() { showFallbackMatches();
console.log('Refreshing fixture data...'); } else {
fetchFixturesData(); console.log('🔍 DEBUG: Data received from WebChannel, no fallback needed');
}, 30000); }
}, 10000);
}); });
// Qt WebChannel initialization (when available) // Qt WebChannel initialization (when available)
if (typeof QWebChannel !== 'undefined') { if (typeof QWebChannel !== 'undefined') {
console.log('🔍 DEBUG: QWebChannel available, initializing...');
new QWebChannel(qt.webChannelTransport, function(channel) { new QWebChannel(qt.webChannelTransport, function(channel) {
console.log('WebChannel initialized for fixtures overlay'); console.log('🔍 DEBUG: WebChannel initialized for fixtures overlay');
console.log('🔍 DEBUG: Available channel objects:', Object.keys(channel.objects));
// Connect to overlay object if available // Connect to overlay object if available
if (channel.objects.overlay) { if (channel.objects.overlay) {
console.log('🔍 DEBUG: Overlay object found in WebChannel');
console.log('🔍 DEBUG: Overlay object methods:', Object.getOwnPropertyNames(channel.objects.overlay));
channel.objects.overlay.dataChanged.connect(function(data) { channel.objects.overlay.dataChanged.connect(function(data) {
console.log('🔍 DEBUG: Overlay dataChanged signal received:', data);
updateOverlayData(data); updateOverlayData(data);
}); });
// Connect fixture data signal
if (channel.objects.overlay.fixtureDataUpdated) {
console.log('🔍 DEBUG: Connecting to fixtureDataUpdated signal');
channel.objects.overlay.fixtureDataUpdated.connect(function(data) {
console.log('🔍 DEBUG: Fixture data signal received:', data);
updateFixtureData(data);
});
} else {
console.log('🔍 DEBUG: fixtureDataUpdated signal not available on overlay object');
}
// Get initial data // Get initial data
if (channel.objects.overlay.getCurrentData) { if (channel.objects.overlay.getCurrentData) {
console.log('🔍 DEBUG: Calling getCurrentData for initial data');
channel.objects.overlay.getCurrentData(function(data) { channel.objects.overlay.getCurrentData(function(data) {
console.log('🔍 DEBUG: Received initial data from getCurrentData:', data);
updateOverlayData(data); updateOverlayData(data);
}); });
} else {
console.log('🔍 DEBUG: getCurrentData method not available');
}
// Try to manually trigger fixtures data load
console.log('🔍 DEBUG: Attempting to manually trigger fixtures data load');
if (channel.objects.overlay.trigger_fixtures_data_load) {
console.log('🔍 DEBUG: Calling trigger_fixtures_data_load manually');
channel.objects.overlay.trigger_fixtures_data_load();
} else if (channel.objects.overlay.sendFixtureDataUpdate) {
console.log('🔍 DEBUG: Calling sendFixtureDataUpdate manually');
channel.objects.overlay.sendFixtureDataUpdate({'test': 'manual_trigger'});
} else {
console.log('🔍 DEBUG: No manual trigger method available');
} }
} else {
console.log('🔍 DEBUG: No overlay object found in WebChannel');
} }
}); });
} else {
console.log('🔍 DEBUG: QWebChannel not available, will rely on DOM ready event');
} }
</script> </script>
......
...@@ -1925,7 +1925,6 @@ def save_intro_templates(): ...@@ -1925,7 +1925,6 @@ def save_intro_templates():
@api_bp.route('/fixtures') @api_bp.route('/fixtures')
@api_bp.auth_manager.require_auth if hasattr(api_bp, 'auth_manager') and api_bp.auth_manager else login_required
def get_fixtures(): def get_fixtures():
"""Get all fixtures/matches grouped by fixture_id with calculated status""" """Get all fixtures/matches grouped by fixture_id with calculated status"""
try: try:
...@@ -2040,10 +2039,14 @@ def calculate_fixture_status(matches, today): ...@@ -2040,10 +2039,14 @@ def calculate_fixture_status(matches, today):
@api_bp.route('/cashier/pending-matches') @api_bp.route('/cashier/pending-matches')
@api_bp.auth_manager.require_auth if hasattr(api_bp, 'auth_manager') and api_bp.auth_manager else login_required
def get_cashier_pending_matches(): def get_cashier_pending_matches():
"""Get pending matches from the correct fixture for cashier dashboard""" """Get pending matches from the correct fixture for cashier dashboard"""
try: try:
# Allow access from localhost without authentication
if request.remote_addr == '127.0.0.1':
pass # Skip authentication
elif hasattr(api_bp, 'auth_manager') and api_bp.auth_manager:
api_bp.auth_manager.require_auth()
from ..database.models import MatchModel from ..database.models import MatchModel
from datetime import datetime, date, timedelta from datetime import datetime, date, timedelta
...@@ -2190,10 +2193,14 @@ def start_games(): ...@@ -2190,10 +2193,14 @@ def start_games():
@api_bp.route('/fixtures/<fixture_id>') @api_bp.route('/fixtures/<fixture_id>')
@api_bp.auth_manager.require_auth if hasattr(api_bp, 'auth_manager') and api_bp.auth_manager else login_required
def get_fixture_details(fixture_id): def get_fixture_details(fixture_id):
"""Get all matches in a fixture by fixture_id""" """Get all matches in a fixture by fixture_id"""
try: try:
# Allow access from localhost without authentication
if request.remote_addr == '127.0.0.1':
pass # Skip authentication
elif hasattr(api_bp, 'auth_manager') and api_bp.auth_manager:
api_bp.auth_manager.require_auth()
from ..database.models import MatchModel, MatchOutcomeModel from ..database.models import MatchModel, MatchOutcomeModel
from datetime import datetime, date from datetime import datetime, date
......
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