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/league_archives/league_20251031_184138.json b/league_archives/league_20251031_184138.json new file mode 100644 index 0000000..27201b5 --- /dev/null +++ b/league_archives/league_20251031_184138.json @@ -0,0 +1,406 @@ +{ + "league": { + "league_id": "league_20251031_184134", + "created_at": "2025-10-31T18:41:34.562185", + "tournament_type": "4_targets", + "total_tournaments": 5, + "current_tournament": 0, + "participants": { + "1": { + "name": "Domen Pleterski", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "2": { + "name": "Nik Pleterski", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "3": { + "name": "Ivan Tandler", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "4": { + "name": "Mateja Pleterski", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "5": { + "name": "Jo\u017ee Verhnjak", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "6": { + "name": "Mateja Senica", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "7": { + "name": "Branko Poker\u017enik", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "8": { + "name": "Franc \u017digart", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "9": { + "name": "Janez Bo\u017ei\u010d", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "10": { + "name": "Mitja \u010ceh", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "11": { + "name": "Rado Kefer", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "12": { + "name": "Matej Kvasnik", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "13": { + "name": "Angelca Mrak", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "14": { + "name": "Karli Proje", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "15": { + "name": "Jan Pleterski", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "16": { + "name": "Silvo Poro\u010dnik", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "17": { + "name": "Du\u0161an Onuk", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "18": { + "name": "Matja\u017e Pleterski", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "19": { + "name": "Franc Rizmal", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "20": { + "name": "Jo\u017ee Preglav", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "21": { + "name": "Marko Blimen", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "22": { + "name": "Doris Fesel", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "23": { + "name": "Robi Krautberger", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "24": { + "name": "Jo\u017ee Verdinek", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "25": { + "name": "Andrej Herman", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "26": { + "name": "Jakob Herman", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "27": { + "name": "Janez Mrak", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "28": { + "name": "An\u017ee Kolar", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "29": { + "name": "Alen Kolar", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "30": { + "name": "Maja Hirtl", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "31": { + "name": "Dejan Ku\u010dnik", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "32": { + "name": "David Strni\u0161a", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "33": { + "name": "Namir Uzunovi\u0107", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "34": { + "name": "Jo\u017ee Planin\u0161ec", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "35": { + "name": "Vanja Kolar", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "36": { + "name": "Klara Wankmuller", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "37": { + "name": "Milan Stramec", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "38": { + "name": "Bojan Sudar", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "39": { + "name": "Tia Sudar", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "40": { + "name": "Jaka Cvar", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "41": { + "name": "Tadej \u0160truc", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "42": { + "name": "Jure Glaser", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "43": { + "name": "Marko Pokr\u017enik", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "44": { + "name": "Anka Ka\u010dnik", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "45": { + "name": "Lidija Blimen", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "46": { + "name": "Tijana \u0160tumpfl", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "47": { + "name": "Ljuba Mr\u0161ak", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "48": { + "name": "Janja Salcman", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "49": { + "name": "Jolanda Verhnjak", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + } + }, + "completed_tournaments": [], + "league_finished": false + }, + "archived_at": "2025-10-31T18:41:38.976988" +} \ No newline at end of file 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... -
-