From 182cc93f2f7b5682b5170a53d85a773a58fcd617 Mon Sep 17 00:00:00 2001 From: bl3kunja Date: Sun, 26 Oct 2025 11:28:01 +0100 Subject: [PATCH] - updated draft with printout - added correction to results for 10s --- app.py | 80 +- locales/en.json | 4 +- locales/sl.json | 4 +- players.json | 2 +- templates/draft.html | 438 ++- .../tournament_20251026_103024.json | 1989 +++++++++++ tournament_results.json | 3138 ++++++++--------- 7 files changed, 3974 insertions(+), 1681 deletions(-) create mode 100644 tournament_archives/tournament_20251026_103024.json diff --git a/app.py b/app.py index a0e4446..84630c3 100644 --- a/app.py +++ b/app.py @@ -44,11 +44,25 @@ def is_mobile_device(): """Check if the request is coming from a mobile device""" user_agent = request.headers.get('User-Agent', '').lower() mobile_patterns = [ - r'android', r'iphone', r'ipad', r'ipod', r'blackberry', + r'android', r'iphone', r'ipad', r'ipod', r'blackberry', r'iemobile', r'opera mini', r'mobile', r'tablet' ] return any(re.search(pattern, user_agent) for pattern in mobile_patterns) +def calculate_tens_from_targets(targets): + """Calculate the number of 10s from a targets dictionary""" + tens_count = 0 + if not targets: + return 0 + + for target in targets.values(): + if isinstance(target, dict): + for shot_key, shot_value in target.items(): + if shot_key.startswith('shot') and shot_value == 10: + tens_count += 1 + + return tens_count + # Define streams globally so both routes can access them STREAMS = [ {'name': 'Target1', 'url': 'http://192.168.0.134:9081'}, @@ -1020,17 +1034,21 @@ def view_archived_tournament(filename): # Process results for display participants = [] for player_id, participant_data in results_data.get('participants', {}).items(): + targets = participant_data.get('targets', {}) + tens_count = calculate_tens_from_targets(targets) + participants.append({ 'id': player_id, 'name': participant_data['name'], 'total_score': participant_data['total_score'], 'completed': participant_data['completed'], - 'targets': participant_data.get('targets', {}) + 'targets': targets, + 'tens_count': tens_count }) - - # Sort by score (descending) - participants.sort(key=lambda x: x['total_score'], reverse=True) - + + # Sort by score (descending), then by tens (descending) as tiebreaker + participants.sort(key=lambda x: (x['total_score'], x['tens_count']), reverse=True) + # Add rankings for i, participant in enumerate(participants): participant['rank'] = i + 1 @@ -1091,16 +1109,20 @@ def mobile_view_archived_tournament(filename): # Process results for display participants = [] for player_id, participant_data in results_data.get('participants', {}).items(): + targets = participant_data.get('targets', {}) + tens_count = calculate_tens_from_targets(targets) + participants.append({ 'id': player_id, 'name': participant_data['name'], 'total_score': participant_data['total_score'], - 'completed': participant_data['completed'] + 'completed': participant_data['completed'], + 'tens_count': tens_count }) - - # Sort by score (descending) - participants.sort(key=lambda x: x['total_score'], reverse=True) - + + # Sort by score (descending), then by tens (descending) as tiebreaker + participants.sort(key=lambda x: (x['total_score'], x['tens_count']), reverse=True) + # Add rankings for i, participant in enumerate(participants): participant['rank'] = i + 1 @@ -1303,25 +1325,29 @@ def mobile_results(): }) return redirect('/mobile/archive') - + # Priority 2: Show individual tournament results (standalone tournament only) elif results and results.get('tournament_finished', False): participants = [] for player_id, data in results['participants'].items(): + targets = data.get('targets', {}) + tens_count = calculate_tens_from_targets(targets) + participants.append({ 'id': player_id, 'name': data['name'], 'total_score': data['total_score'], - 'completed': data['completed'] + 'completed': data['completed'], + 'tens_count': tens_count }) - - # Sort by score (descending) - participants.sort(key=lambda x: x['total_score'], reverse=True) - + + # Sort by score (descending), then by tens (descending) as tiebreaker + participants.sort(key=lambda x: (x['total_score'], x['tens_count']), reverse=True) + # Add rankings for i, participant in enumerate(participants): participant['rank'] = i + 1 - + return render_template('mobile_results.html', results=results, participants=participants, @@ -1492,25 +1518,29 @@ def results_display(): # If we can't find the archive, redirect to archive page return redirect('/archive') - + # Priority 2: Show individual tournament results (standalone tournament only) elif results and results.get('tournament_finished', False): participants = [] for player_id, data in results['participants'].items(): + targets = data.get('targets', {}) + tens_count = calculate_tens_from_targets(targets) + participants.append({ 'id': player_id, 'name': data['name'], 'total_score': data['total_score'], - 'completed': data['completed'] + 'completed': data['completed'], + 'tens_count': tens_count }) - - # Sort by score (descending) - participants.sort(key=lambda x: x['total_score'], reverse=True) - + + # Sort by score (descending), then by tens (descending) as tiebreaker + participants.sort(key=lambda x: (x['total_score'], x['tens_count']), reverse=True) + # Add rankings for i, participant in enumerate(participants): participant['rank'] = i + 1 - + return render_template('results_display.html', results=results, participants=participants, diff --git a/locales/en.json b/locales/en.json index dd1a3c0..95b4144 100644 --- a/locales/en.json +++ b/locales/en.json @@ -336,7 +336,9 @@ "set_up_tournament": "Set Up Tournament", "updating": "Updating...", "manage": "Manage", - "dashboard": "Dashboard" + "dashboard": "Dashboard", + "time": "Time", + "completed": "Completed" }, "time": { "monday": "Monday", diff --git a/locales/sl.json b/locales/sl.json index 3d6d67e..160c28c 100644 --- a/locales/sl.json +++ b/locales/sl.json @@ -343,7 +343,9 @@ "set_up_tournament": "Nastavitev Turnirja", "updating": "Posodabljam...", "manage": "Upravljaj", - "dashboard": "Nadzorna Plošča" + "dashboard": "Nadzorna Plošča", + "time": "Čas", + "completed": "Zaključeno" }, "time": { "monday": "Ponedeljek", diff --git a/players.json b/players.json index a2e8e3f..cfa99aa 100644 --- a/players.json +++ b/players.json @@ -143,7 +143,7 @@ { "id": 29, "name": "Alen Kolar", - "enabled": false + "enabled": true }, { "id": 30, diff --git a/templates/draft.html b/templates/draft.html index 3419101..10c2ab4 100644 --- a/templates/draft.html +++ b/templates/draft.html @@ -253,50 +253,89 @@ } .position-card { - background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%); - border: 2px solid #e9ecef; - border-radius: 8px; - padding: 8px 6px; - text-align: center; + background: white; + border: 2px solid #9ca3af; + border-radius: 10px; + overflow: hidden; + display: flex; + flex-direction: row; + transition: all 0.3s ease; + box-shadow: 0 2px 6px rgba(156, 163, 175, 0.2); + min-height: 70px; + } + + /* Current round - blue cards */ + .round-row.current .position-card { + border-color: #5a8fd1; + box-shadow: 0 2px 6px rgba(90, 143, 209, 0.2); + } + + .round-row.current .position-card .position-header { + background: #5a8fd1; + } + + /* Completed round - green cards */ + .round-row.completed .position-card { + border-color: #28a745; + box-shadow: 0 2px 6px rgba(40, 167, 69, 0.2); + } + + .round-row.completed .position-card .position-header { + background: #28a745; + } + + /* Waiting round - gray cards */ + .round-row.waiting .position-card { + border-color: #9ca3af; + box-shadow: 0 2px 6px rgba(156, 163, 175, 0.2); + } + + .round-row.waiting .position-card .position-header { + background: #9ca3af; + } + + .position-header { + background: #9ca3af; + padding: 0; + display: flex; + align-items: center; + justify-content: center; + flex-shrink: 0; + min-width: 50px; + width: 50px; + } + + .position-card.empty .position-header { + background: #9ca3af; + } + + .position-number { + font-size: 1.6rem; + font-weight: 700; + color: white; + line-height: 1; + } + + .position-body { + padding: 10px 12px; display: flex; flex-direction: column; justify-content: center; align-items: center; - transition: all 0.3s ease; - box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08); - min-height: 60px; + flex: 1; + background: white; + border-radius: 0 8px 8px 0; } - .position-card.filled { - border-color: #28a745; - background: linear-gradient(135deg, #f8fff9 0%, #ffffff 100%); - box-shadow: 0 3px 10px rgba(40, 167, 69, 0.15); - } - - .position-card.empty { - border-color: #dee2e6; - background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%); - opacity: 0.8; - } - - .position-number { - font-size: 1.2rem; - font-weight: 700; - color: #007bff; - margin-bottom: 4px; - line-height: 1; - } - - .position-card.empty .position-number { - color: #6c757d; + .position-card.empty .position-body { + background: #f8f9fa; } .player-name { - font-size: 1rem; + font-size: 1.15rem; font-weight: 600; color: #2c3e50; - line-height: 1.2; - margin-bottom: 4px; + line-height: 1.3; word-wrap: break-word; max-width: 100%; text-align: center; @@ -309,14 +348,7 @@ } .player-id { - background: #28a745; - color: white; - padding: 2px 6px; - border-radius: 8px; - font-size: 0.65rem; - font-weight: 600; - display: inline-block; - letter-spacing: 0.3px; + display: none; } /* No Tournament State */ @@ -341,24 +373,251 @@ font-size: 1.1rem; } - /* Auto-refresh indicator */ - .refresh-indicator { - position: fixed; - bottom: 20px; - right: 20px; - background: rgba(0, 123, 255, 0.9); - color: white; - padding: 8px 15px; - border-radius: 20px; - font-size: 0.8rem; - font-weight: bold; - opacity: 0; - transition: opacity 0.3s ease; - z-index: 999; + /* PRINT STYLES */ + @media print { + .navbar, + .tournament-controls { + display: none !important; + } + + html, body { + height: auto !important; + overflow: visible !important; + background: white !important; + margin: 0; + padding: 20px; + } + + .main-container { + height: auto !important; + padding: 0 !important; + } + + .tournament-header { + background: white !important; + color: #333 !important; + box-shadow: none !important; + border: 2px solid #ddd !important; + padding: 20px; + margin-bottom: 20px; + page-break-inside: avoid; + } + + .tournament-title { + font-size: 24pt !important; + font-weight: bold !important; + color: #333 !important; + margin-bottom: 10px; + } + + .tournament-stats { + font-size: 12pt !important; + color: #666 !important; + margin-bottom: 0 !important; + } + + .current-round-info { + display: none !important; + } + + .rounds-container { + display: block !important; + overflow: visible !important; + padding: 0 !important; + } + + .round-row { + background: white !important; + border: 1px solid #ddd !important; + border-radius: 0 !important; + box-shadow: none !important; + margin-bottom: 15px; + page-break-inside: avoid; + display: block !important; + } + + .round-row.current, + .round-row.completed, + .round-row.waiting { + border-left: 1px solid #ddd !important; + opacity: 1 !important; + } + + .round-header { + background: #f8f9fa !important; + border: none !important; + border-bottom: 1px solid #ddd !important; + display: flex !important; + justify-content: space-between !important; + align-items: center !important; + padding: 10px 15px !important; + } + + .round-row.current .round-header, + .round-row.completed .round-header, + .round-row.waiting .round-header { + background: #f8f9fa !important; + } + + .round-title { + font-size: 14pt !important; + font-weight: bold !important; + flex-shrink: 0; + } + + .round-badge { + display: none !important; + } + + .round-print-info { + display: flex !important; + align-items: center !important; + gap: 20px !important; + } + + .round-time-field { + display: flex !important; + align-items: center !important; + gap: 8px !important; + } + + .round-time-label { + font-size: 11pt !important; + color: #666 !important; + font-weight: 500 !important; + } + + .round-time-input { + border: 2px solid #333 !important; + border-radius: 4px !important; + padding: 6px 10px !important; + width: 100px !important; + height: 32px !important; + background: white !important; + } + + .round-checkbox-field { + display: flex !important; + align-items: center !important; + gap: 10px !important; + } + + .round-checkbox-label { + font-size: 11pt !important; + color: #666 !important; + font-weight: 500 !important; + } + + .round-checkbox { + width: 24px !important; + height: 24px !important; + border: 2px solid #333 !important; + border-radius: 4px !important; + background: white !important; + display: inline-block !important; + } + + .positions-container { + display: grid !important; + grid-template-columns: repeat(3, 1fr) !important; + gap: 10px !important; + padding: 15px !important; + } + + .position-card { + border: 1px solid #5a8fd1 !important; + border-radius: 8px !important; + overflow: hidden !important; + background: white !important; + box-shadow: none !important; + min-height: 60px !important; + page-break-inside: avoid; + display: flex !important; + flex-direction: row !important; + } + + /* All cards in print have same blue color */ + .round-row.current .position-card, + .round-row.completed .position-card, + .round-row.waiting .position-card { + border-color: #5a8fd1 !important; + } + + .position-header { + background: #5a8fd1 !important; + padding: 0 !important; + display: flex !important; + align-items: center !important; + justify-content: center !important; + flex-shrink: 0 !important; + min-width: 40px !important; + width: 40px !important; + } + + .round-row.current .position-card .position-header, + .round-row.completed .position-card .position-header, + .round-row.waiting .position-card .position-header { + background: #5a8fd1 !important; + } + + .position-card.empty .position-header { + background: #9ca3af !important; + } + + .position-number { + font-size: 14pt !important; + color: white !important; + } + + .position-body { + padding: 8px 10px !important; + background: white !important; + flex: 1 !important; + display: flex !important; + flex-direction: column !important; + justify-content: center !important; + align-items: center !important; + } + + .position-card.empty .position-body { + background: #f8f9fa !important; + } + + .player-name { + font-size: 11pt !important; + color: #2c3e50 !important; + } + + .position-card.empty .player-name { + color: #6c757d !important; + } + + .player-id { + display: none !important; + } + + .no-tournament { + display: none !important; + } + + /* Print header - show logo in tournament header */ + .print-header { + display: none !important; + } + + .tournament-header .print-logo { + display: block !important; + height: 60px !important; + max-width: 160px !important; + margin: 0 auto 10px auto !important; + } } - .refresh-indicator.show { - opacity: 1; + /* Print-only elements - hidden on screen */ + .print-header, + .round-print-info, + .tournament-header .print-logo { + display: none; } @@ -372,17 +631,24 @@ 📚 Archive 📋 Draft 🎯 Results Calculator + + + +
{% if tournament %}
+
🎯 Shooting Tournament
{{ tournament.total_players }} players • {{ tournament.total_rounds }} rounds {% if tournament.current_round %} - • Currently on Round {{ tournament.current_round }} + Currently on Round {{ tournament.current_round }} {% endif %}
@@ -413,19 +679,35 @@ {% else %}
Wait
{% endif %} + + +
+
+ Time: +
+
+
+ Completed: +
+
+
{% for position in range(1, 7) %} {% set player = round.players[position-1] if position <= round.players|length else none %}
-
{{ position }}
- {% if player %} -
{{ player.name }}
-
ID: {{ player.id }}
- {% else %} -
Empty
- {% endif %} +
+
{{ position }}
+
+
+ {% if player %} +
{{ player.name }}
+
ID: {{ player.id }}
+ {% else %} +
Empty
+ {% endif %} +
{% endfor %}
@@ -442,11 +724,6 @@ {% endif %}
- -
- 🔄 Updating... -
-