From 27e8b31ae04b27245fe92a516f0624e8e6e9be7c Mon Sep 17 00:00:00 2001 From: bl3kunja-FW Date: Fri, 14 Nov 2025 17:03:30 +0100 Subject: [PATCH 1/2] combining league jsons --- locales/en.json | 31 +- locales/sl.json | 30 +- static/js/i18n.js | 9 +- templates/draft.html | 4 +- templates/index.html | 8 +- templates/league_combine.html | 771 +++++++++++++++++++++++ templates/league_scoreboard_display.html | 26 + templates/results_display.html | 28 + tv_app.py | 338 +++++++++- 9 files changed, 1231 insertions(+), 14 deletions(-) create mode 100644 templates/league_combine.html diff --git a/locales/en.json b/locales/en.json index 8529c70..b66bcb5 100644 --- a/locales/en.json +++ b/locales/en.json @@ -22,6 +22,7 @@ "enable_selected": "Enable Selected", "disable_selected": "Disable Selected", "print": "Print", + "export": "Export JSON", "visible": "Visible", "date": "Date", "view": "View", @@ -140,6 +141,33 @@ "highest_score": "Highest Score", "average_final": "Average Final", "5_tournament_league": "5 Tournament League - Best 4 Count", + "combine_leagues": "Combine Leagues", + "combine_mode": "Combine Leagues", + "convert_mode": "Convert Tournaments to League", + "upload_league_files": "Upload League JSON Files", + "upload_tournament_files": "Upload Tournament JSON Files", + "combine_leagues_desc": "Upload multiple league JSON files to combine them into a single results table.", + "convert_tournaments_desc": "Upload 1-5 tournament JSON files to create a league. You can upload partial leagues as tournaments complete. Final scoring uses best 4 results when all 5 are uploaded.", + "click_browse": "Click to browse", + "drag_drop_files": "or drag and drop league JSON files here", + "supports_multiple": "Supports multiple file selection", + "clear_all": "Clear All", + "combine_preview": "Combine & Preview", + "convert_to_league": "Convert to League", + "valid_league": "Valid League", + "valid_tournament": "Valid Tournament", + "participants": "participants", + "tournaments": "tournaments", + "invalid_format": "Invalid file format", + "select_mode": "Select Mode", + "start_over": "Start Over", + "export_combined_results": "Export Combined Results", + "combined_league_info": "Combined League Information", + "uploaded_files": "Uploaded Files", + "remove": "Remove", + "type": "Type", + "failed_parse_json": "Failed to parse JSON", + "invalid_tournament_format": "Invalid tournament file format", "joker_used_badge": "Joker Used", "search_players_placeholder": "Search players by name...", "no_players_found": "No players found matching your search criteria.", @@ -166,7 +194,8 @@ "system": "System", "camera": "Camera", "tournaments": "Tournaments", - "results.most_tens": "Most 10s" + "results.most_tens": "Most 10s", + "combine_leagues": "Combine Leagues" }, "results": { "results": "Results", diff --git a/locales/sl.json b/locales/sl.json index dd1a415..b1fa566 100644 --- a/locales/sl.json +++ b/locales/sl.json @@ -22,6 +22,7 @@ "enable_selected": "Omogoči Izbrane", "disable_selected": "Onemogoči Izbrane", "print": "Natisni", + "export": "Izvozi JSON", "visible": "Vidni", "date": "Datum", "view": "Oglej", @@ -171,8 +172,33 @@ "system": "Sistem", "camera": "Kamera", "tournaments": "Turnirji", - "results.most_tens": "Največ Desetk" - + "results.most_tens": "Največ Desetk", + "combine_leagues": "Združi Lige", + "combine_mode": "Združi Lige", + "convert_mode": "Pretvori Turnirje v Ligo", + "upload_league_files": "Naloži Datoteke Lig", + "upload_tournament_files": "Naloži Datoteke Turnirjev", + "combine_leagues_desc": "Naloži več datotek lig JSON, da jih združiš v eno tabelo rezultatov.", + "convert_tournaments_desc": "Naloži 1-5 datotek turnirjev JSON za ustvarjanje lige. Lahko naložiš delne lige ko turnirji napredujejo. Končno točkovanje uporablja najboljše 4 rezultate, ko so vseh 5 naloženih.", + "click_browse": "Klikni za brskanje", + "drag_drop_files": "ali povleci in spusti datoteke JSON lig tukaj", + "supports_multiple": "Podpira izbiro več datotek", + "combine_preview": "Združi in Predoglej", + "convert_to_league": "Pretvori v Ligo", + "valid_league": "Veljavna Liga", + "valid_tournament": "Veljaven Turnir", + "participants": "udeleženci", + "invalid_format": "Neveljavna oblika datoteke", + "select_mode": "Izberi Način", + "start_over": "Začni Znova", + "export_combined_results": "Izvozi Združene Rezultate", + "combined_league_info": "Informacije o Združeni Ligi", + "uploaded_files": "Naložene Datoteke", + "remove": "Odstrani", + "type": "Tip", + "failed_parse_json": "Napaka pri branju JSON", + "invalid_tournament_format": "Neveljavna oblika datoteke turnirja" + }, "results": { "results": "Rezultati", diff --git a/static/js/i18n.js b/static/js/i18n.js index dfe1e98..15a755f 100644 --- a/static/js/i18n.js +++ b/static/js/i18n.js @@ -274,4 +274,11 @@ document.addEventListener('DOMContentLoaded', initI18n); // Export functions for global use window.t = t; window.changeLanguage = changeLanguage; -window.createLanguageSelector = createLanguageSelector; \ No newline at end of file +window.createLanguageSelector = createLanguageSelector; + +// Export i18n object with useful functions +window.i18n = { + t: t, + updatePageTranslations: translatePage, + changeLanguage: changeLanguage +}; \ No newline at end of file diff --git a/templates/draft.html b/templates/draft.html index b23d1c6..780220a 100644 --- a/templates/draft.html +++ b/templates/draft.html @@ -122,7 +122,7 @@ } .round-row.current { - border-left: 4px solid #28a745; + border-left: 4px solid #007bff; box-shadow: 0 4px 15px rgba(0, 123, 255, 0.25); } @@ -171,7 +171,7 @@ } .current-badge { - background: #28a745; + background: #007bff; color: white; } diff --git a/templates/index.html b/templates/index.html index 3dda3ab..338b3bb 100644 --- a/templates/index.html +++ b/templates/index.html @@ -683,21 +683,17 @@
📋 Oglej si Celoten Žreb Turnirja + 🎯 Calculator ⚙️ Upravljaj Turnir
{% endif %} - {% if settings.tournament_active %} -
- 🎯 Calculator -
- {% endif %} -

Turnirji

🏆 Način Turnirja + 🔗 Combine Leagues 👤 Analiza Igralcev 📚 Arhiv
diff --git a/templates/league_combine.html b/templates/league_combine.html new file mode 100644 index 0000000..99e09d0 --- /dev/null +++ b/templates/league_combine.html @@ -0,0 +1,771 @@ + + + + + + Combine Leagues + + + + + + + + + +
+
+
+ +
+ + +
+
+ +
+

📤 Upload League JSON Files

+

+ Upload multiple league JSON files to combine them into a single results table. +

+
+ + + +
+
📁
+

Click to browse or drag and drop league JSON files here

+

Supports multiple file selection

+ +
+ +
+ +
+ + +
+
+ +
+
+

ℹ️ Combined League Information

+
+
+ +
+ +
+ +
+ + +
+
+
+ + + + + diff --git a/templates/league_scoreboard_display.html b/templates/league_scoreboard_display.html index 4fa43aa..f7661bf 100644 --- a/templates/league_scoreboard_display.html +++ b/templates/league_scoreboard_display.html @@ -787,6 +787,7 @@ @@ -1199,6 +1200,31 @@ // Initialize when page loads document.addEventListener('DOMContentLoaded', initializePage); + // Export league data as JSON + function exportLeagueJSON() { + const leagueData = {{ league | tojson | safe }}; + + // Wrap in archive format for compatibility + const archiveData = { + league: leagueData, + archived_at: new Date().toISOString() + }; + + const dataStr = JSON.stringify(archiveData, null, 2); + const dataBlob = new Blob([dataStr], { type: 'application/json' }); + const url = URL.createObjectURL(dataBlob); + const link = document.createElement('a'); + link.href = url; + + // Generate filename with league info + const leagueId = leagueData.league_id || 'league'; + const date = new Date().toISOString().slice(0, 10); + link.download = `${leagueId}_${date}.json`; + + link.click(); + URL.revokeObjectURL(url); + } + // Keyboard shortcuts document.addEventListener('keydown', function(event) { if (event.key === 'r' || event.key === 'R') { diff --git a/templates/results_display.html b/templates/results_display.html index 5e8ee2b..d616b26 100644 --- a/templates/results_display.html +++ b/templates/results_display.html @@ -657,6 +657,7 @@ @@ -962,6 +963,33 @@ } } + // Export results as JSON + function exportResultsJSON() { + const tournamentData = {{ tournament | tojson | safe }}; + const resultsData = {{ results | tojson | safe }}; + + // Wrap in archive format for compatibility + const archiveData = { + tournament: tournamentData, + results: resultsData, + archived_at: new Date().toISOString() + }; + + const dataStr = JSON.stringify(archiveData, null, 2); + const dataBlob = new Blob([dataStr], { type: 'application/json' }); + const url = URL.createObjectURL(dataBlob); + const link = document.createElement('a'); + link.href = url; + + // Generate filename with tournament info + const tournamentId = tournamentData.tournament_id || 'tournament'; + const date = new Date().toISOString().slice(0, 10); + link.download = `${tournamentId}_${date}.json`; + + link.click(); + URL.revokeObjectURL(url); + } + // Initialize when page loads document.addEventListener('DOMContentLoaded', initializePage); diff --git a/tv_app.py b/tv_app.py index 25ee86d..ebc29c7 100644 --- a/tv_app.py +++ b/tv_app.py @@ -1087,7 +1087,21 @@ def results_display(): translations=get_translations(), current_language=get_current_language()) - # Priority 1.5: Check if current results are from a finished league (even if league state was archived) + # Priority 1.5: Check if results contain league_data (from league preview/combiner) + elif results and results.get('league_data'): + league_data = results.get('league_data') + calculate_league_final_scores(league_data) + participants = get_league_final_rankings(league_data) + + return render_template('league_scoreboard_display.html', + league=league_data, + participants=participants, + results=None, + preview_mode=True, + translations=get_translations(), + current_language=get_current_language()) + + # Priority 1.6: Check if current results are from a finished league (even if league state was archived) elif results and results.get('league_tournament_number'): # This is a league tournament result, but league state was archived # Try to find the archived league data @@ -1697,7 +1711,327 @@ def get_league(): return jsonify(league_state) else: return jsonify({'status': 'error', 'message': 'No league found'}), 404 - + +@app.route('/league/combine') +def league_combine_page(): + """Page for combining multiple league JSON files""" + if is_mobile_device(): + return redirect('/mobile') + + return render_template('league_combine.html', + translations=get_translations(), + current_language=get_current_language()) + +@app.route('/league/preview') +def league_preview(): + """Display league preview from session""" + # Get league data from session + league_data = session.get('preview_league') + + if not league_data: + return redirect('/league/combine') + + # Calculate scores and get rankings + calculate_league_final_scores(league_data) + participants = get_league_final_rankings(league_data) + + return render_template('league_scoreboard_display.html', + league=league_data, + participants=participants, + results=None, + preview_mode=True, + translations=get_translations(), + current_language=get_current_language()) + +@app.route('/league/set-preview', methods=['POST']) +def set_league_preview(): + """Store league data in session for preview""" + try: + data = request.get_json() + combined_league = data.get('league') + + if not combined_league: + return jsonify({'status': 'error', 'message': 'No league data provided'}), 400 + + # Store in session + session['preview_league'] = combined_league + + return jsonify({ + 'status': 'success', + 'redirect_url': '/league/preview' + }) + except Exception as e: + print(f"Error setting league preview: {e}") + return jsonify({'status': 'error', 'message': str(e)}), 500 + +@app.route('/api/league/combine', methods=['POST']) +def combine_leagues(): + """API endpoint to combine multiple league files""" + try: + data = request.get_json() + leagues = data.get('leagues', []) + + if len(leagues) < 1: + return jsonify({'status': 'error', 'message': 'Need at least 1 league'}), 400 + + # If only one league, just return it for preview + if len(leagues) == 1: + league = leagues[0] + calculate_league_final_scores(league) + participants = get_league_final_rankings(league) + + highest_score = max((p['final_score'] for p in participants), default=0) + avg_score = sum(p['final_score'] for p in participants) / len(participants) if participants else 0 + + return jsonify({ + 'status': 'success', + 'combined_league': league, + 'participants': participants, + 'source_count': 1, + 'tournament_type': league.get('tournament_type', '20_targets'), + 'total_participants': len(participants), + 'total_tournaments': league.get('total_tournaments', 5), + 'highest_score': highest_score, + 'avg_score': round(avg_score, 2) + }) + + # Get tournament type from first league + tournament_type = leagues[0].get('tournament_type', '20_targets') + + # Create combined league structure + combined_league = { + 'league_id': f'combined_{datetime.now().strftime("%Y%m%d_%H%M%S")}', + 'created_at': datetime.now().isoformat(), + 'tournament_type': tournament_type, + 'total_tournaments': sum(len(league.get('completed_tournaments', [])) for league in leagues), + 'current_tournament': 0, + 'participants': {}, + 'completed_tournaments': [], + 'league_finished': True, + 'combined_from': len(leagues) + } + + # Build name-to-player mapping for combining by name + name_to_combined_id = {} # Maps player name to their ID in combined league + next_player_id = 1 + + # Combine tournament results from all leagues + tournament_counter = 0 + for league_idx, league in enumerate(leagues): + # Process each participant in this league + for player_id, league_participant in league.get('participants', {}).items(): + player_name = league_participant.get('name', f'Player {player_id}') + + # Check if we've seen this player name before + if player_name in name_to_combined_id: + # Use existing combined player + combined_id = name_to_combined_id[player_name] + else: + # Create new combined player + combined_id = str(next_player_id) + next_player_id += 1 + name_to_combined_id[player_name] = combined_id + + # Initialize new participant + combined_league['participants'][combined_id] = { + 'name': player_name, + 'joker_used': False, + 'tournament_results': [], + 'total_score': 0, + 'final_score': 0, + 'tournaments_participated': 0 + } + + combined_participant = combined_league['participants'][combined_id] + + # Add all tournament results from this league + for result in league_participant.get('tournament_results', []): + combined_participant['tournament_results'].append({ + 'tournament': tournament_counter + result.get('tournament', 0), + 'score': result.get('score', 0), + 'tens_count': result.get('tens_count', 0), + 'participated': result.get('participated', True), + 'source_league': league_idx + 1 + }) + + if result.get('participated', True): + combined_participant['tournaments_participated'] += 1 + combined_participant['total_score'] += result.get('score', 0) + + # Add completed tournaments + for completed in league.get('completed_tournaments', []): + combined_league['completed_tournaments'].append({ + **completed, + 'source_league': league_idx + 1 + }) + + tournament_counter += len(league.get('completed_tournaments', [])) + + # Calculate final scores + calculate_league_final_scores(combined_league) + + # Get rankings + participants = get_league_final_rankings(combined_league) + + # Calculate stats + highest_score = max((p['final_score'] for p in participants), default=0) + avg_score = sum(p['final_score'] for p in participants) / len(participants) if participants else 0 + + return jsonify({ + 'status': 'success', + 'combined_league': combined_league, + 'participants': participants, + 'source_count': len(leagues), + 'tournament_type': tournament_type, + 'total_participants': len(participants), + 'total_tournaments': combined_league['total_tournaments'], + 'highest_score': highest_score, + 'avg_score': round(avg_score, 2) + }) + + except Exception as e: + print(f"Error combining leagues: {e}") + import traceback + traceback.print_exc() + return jsonify({'status': 'error', 'message': str(e)}), 400 + +@app.route('/api/league/convert', methods=['POST']) +def convert_tournaments_to_league(): + """API endpoint to convert 1-5 tournament files into a league""" + try: + data = request.get_json() + tournaments = data.get('tournaments', []) + + if len(tournaments) < 1 or len(tournaments) > 5: + return jsonify({'status': 'error', 'message': 'Need 1-5 tournament files'}), 400 + + # Sort tournaments by finished_at timestamp to maintain chronological order + tournaments.sort(key=lambda t: t.get('results', {}).get('finished_at', '')) + + # Get tournament type from first tournament + tournament_type = tournaments[0].get('results', {}).get('tournament_type', '20_targets') + num_tournaments = len(tournaments) + + # Collect all unique participants + all_player_ids = set() + for tournament in tournaments: + results = tournament.get('results', {}) + all_player_ids.update(results.get('participants', {}).keys()) + + # Create league structure + created_league = { + 'league_id': f'converted_{datetime.now().strftime("%Y%m%d_%H%M%S")}', + 'created_at': datetime.now().isoformat(), + 'tournament_type': tournament_type, + 'total_tournaments': 5, # Always 5 for a full league + 'current_tournament': num_tournaments, # How many completed so far + 'participants': {}, + 'completed_tournaments': [], + 'league_finished': num_tournaments >= 5, # Only finished if all 5 uploaded + 'converted_from_tournaments': True, + 'is_partial': num_tournaments < 5 + } + + # Initialize participants + for player_id in all_player_ids: + created_league['participants'][player_id] = { + 'name': '', + 'joker_used': False, + 'tournament_results': [], + 'total_score': 0, + 'final_score': 0, + 'tournaments_participated': 0 + } + + # Process each tournament + for tournament_idx, tournament in enumerate(tournaments): + results = tournament.get('results', {}) + tournament_results_data = results.get('participants', {}) + + # Add completed tournament metadata + created_league['completed_tournaments'].append({ + 'tournament_number': tournament_idx + 1, + 'tournament_type': results.get('tournament_type', tournament_type), + 'finished_at': results.get('finished_at', datetime.now().isoformat()), + 'results_summary': { + 'participants': len(tournament_results_data), + 'tournament_id': tournament.get('tournament', {}).get('tournament_id', f'tournament_{tournament_idx + 1}') + } + }) + + # Add results for each participant + for player_id in all_player_ids: + league_participant = created_league['participants'][player_id] + + if player_id in tournament_results_data: + participant_data = tournament_results_data[player_id] + + # Set name if not set + if not league_participant['name']: + league_participant['name'] = participant_data.get('name', f'Player {player_id}') + + # Count tens + tens_count = 0 + targets = participant_data.get('targets', {}) + for target in targets.values(): + for shot_key, shot_value in target.items(): + if shot_key.startswith('shot') and shot_value == 10: + tens_count += 1 + + # Add tournament result + score = participant_data.get('total_score', 0) + league_participant['tournament_results'].append({ + 'tournament': tournament_idx + 1, + 'score': score, + 'tens_count': tens_count, + 'participated': True + }) + + league_participant['tournaments_participated'] += 1 + league_participant['total_score'] += score + else: + # Player didn't participate (used joker) + league_participant['tournament_results'].append({ + 'tournament': tournament_idx + 1, + 'score': 0, + 'tens_count': 0, + 'participated': False, + 'joker': True + }) + + if tournament_idx == 0: + # Set joker_used for players who didn't participate in first tournament + league_participant['joker_used'] = True + + # Calculate final scores using league rules + calculate_league_final_scores(created_league) + + # Get rankings + participants = get_league_final_rankings(created_league) + + # Calculate stats + highest_score = max((p['final_score'] for p in participants), default=0) + avg_score = sum(p['final_score'] for p in participants) / len(participants) if participants else 0 + + return jsonify({ + 'status': 'success', + 'combined_league': created_league, + 'participants': participants, + 'source_count': num_tournaments, + 'tournament_type': tournament_type, + 'total_participants': len(participants), + 'total_tournaments': num_tournaments, + 'is_partial': num_tournaments < 5, + 'highest_score': highest_score, + 'avg_score': round(avg_score, 2) + }) + + except Exception as e: + print(f"Error converting tournaments to league: {e}") + import traceback + traceback.print_exc() + return jsonify({'status': 'error', 'message': str(e)}), 400 + # Add this route to your Flask app (around line 850, with the other mobile routes) @app.route('/mobile/remote') From 13c7bd32391227d290d41040ed92eafe62608e9b Mon Sep 17 00:00:00 2001 From: bl3kunja-FW Date: Sat, 17 Jan 2026 09:36:27 +0100 Subject: [PATCH 2/2] Liga krog3 --- app/config.py | 2 +- app/models.py | 3 +- data/camera_settings.json | 4 +- .../league_20251115_093906.json | 134 ++ data/league_state.json | 457 ++++++ data/players.json | 80 +- data/tournament_results.json | 1242 +++++++++++++++++ locales/en.json | 3 +- locales/sl.json | 3 +- templates/league_scoreboard_display.html | 2 + templates/results_calculator.html | 113 +- templates/tournament.html | 27 +- tv_app.py | 3 +- 13 files changed, 2027 insertions(+), 46 deletions(-) create mode 100644 data/league_archives/league_20251115_093906.json create mode 100644 data/league_state.json create mode 100644 data/tournament_results.json diff --git a/app/config.py b/app/config.py index a0c50fe..8ff067a 100644 --- a/app/config.py +++ b/app/config.py @@ -10,4 +10,4 @@ class Config: SECRET_KEY = os.getenv('SECRET_KEY', 'your-secret-key-for-sessions') DEBUG = os.getenv('DEBUG', 'False').lower() == 'true' HOST = os.getenv('HOST', '0.0.0.0') - PORT = int(os.getenv('PORT', 5000)) + PORT = int(os.getenv('PORT', 5001)) diff --git a/app/models.py b/app/models.py index 0d570f0..1640b76 100644 --- a/app/models.py +++ b/app/models.py @@ -131,7 +131,8 @@ class Tournament: 'name': player['name'], 'targets': targets, 'total_score': 0, - 'completed': False + 'completed': False, + 'joker_selected': False # Track if joker was selected in calculator } return results diff --git a/data/camera_settings.json b/data/camera_settings.json index af4f44b..81fd99c 100644 --- a/data/camera_settings.json +++ b/data/camera_settings.json @@ -9,7 +9,7 @@ }, "display_options": { "show_titles": true, - "title_size": 1.2, - "target_number_size": 1.4 + "title_size": 1.4, + "target_number_size": 1.7 } } \ No newline at end of file diff --git a/data/league_archives/league_20251115_093906.json b/data/league_archives/league_20251115_093906.json new file mode 100644 index 0000000..abdd081 --- /dev/null +++ b/data/league_archives/league_20251115_093906.json @@ -0,0 +1,134 @@ +{ + "league": { + "league_id": "league_20251115_093831", + "created_at": "2025-11-15T09:38:31.193555", + "tournament_type": "20_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 + }, + "5": { + "name": "Jože Verhnjak", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "7": { + "name": "Branko Pokeržnik", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "9": { + "name": "Janez Božič", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "10": { + "name": "Mitja Čeh", + "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 + }, + "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čnik", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "17": { + "name": "Dušan Onuk", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "18": { + "name": "Matjaž Pleterski", + "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 + }, + "24": { + "name": "Jože Verdinek", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "50": { + "name": "Vid Ravnjak", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "51": { + "name": "Robi Ovčar", + "joker_used": false, + "tournament_results": [], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + } + }, + "completed_tournaments": [], + "league_finished": false + }, + "archived_at": "2025-11-15T09:39:06.974580" +} \ No newline at end of file diff --git a/data/league_state.json b/data/league_state.json new file mode 100644 index 0000000..61ca278 --- /dev/null +++ b/data/league_state.json @@ -0,0 +1,457 @@ +{ + "league_id": "league_20251115_094741", + "created_at": "2025-11-15T09:47:41.711914", + "tournament_type": "20_targets", + "total_tournaments": 5, + "current_tournament": 3, + "participants": { + "1": { + "name": "Domen Pleterski", + "joker_used": true, + "tournament_results": [ + { + "tournament": 1, + "score": 320, + "tens_count": 3, + "participated": true + }, + { + "tournament": 2, + "score": 0, + "tens_count": 0, + "participated": false, + "joker": true + }, + { + "tournament": 3, + "score": 320, + "tens_count": 2, + "participated": true + } + ], + "total_score": 640, + "final_score": 0, + "tournaments_participated": 0 + }, + "5": { + "name": "Jože Verhnjak", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 295, + "tens_count": 2, + "participated": true + }, + { + "tournament": 2, + "score": 293, + "tens_count": 3, + "participated": true + }, + { + "tournament": 3, + "score": 293, + "tens_count": 2, + "participated": true + } + ], + "total_score": 881, + "final_score": 0, + "tournaments_participated": 0 + }, + "7": { + "name": "Branko Pokeržnik", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 316, + "tens_count": 1, + "participated": true + }, + { + "tournament": 2, + "score": 332, + "tens_count": 3, + "participated": true + }, + { + "tournament": 3, + "score": 318, + "tens_count": 1, + "participated": true + } + ], + "total_score": 966, + "final_score": 0, + "tournaments_participated": 0 + }, + "9": { + "name": "Janez Božič", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 264, + "tens_count": 2, + "participated": true + }, + { + "tournament": 2, + "score": 276, + "tens_count": 0, + "participated": true + }, + { + "tournament": 3, + "score": 261, + "tens_count": 1, + "participated": true + } + ], + "total_score": 801, + "final_score": 0, + "tournaments_participated": 0 + }, + "10": { + "name": "Mitja Čeh", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 329, + "tens_count": 1, + "participated": true + }, + { + "tournament": 2, + "score": 312, + "tens_count": 4, + "participated": true + }, + { + "tournament": 3, + "score": 307, + "tens_count": 2, + "participated": true + } + ], + "total_score": 948, + "final_score": 0, + "tournaments_participated": 0 + }, + "11": { + "name": "Rado Kefer", + "joker_used": true, + "tournament_results": [ + { + "tournament": 1, + "score": 275, + "tens_count": 0, + "participated": true + }, + { + "tournament": 2, + "score": 0, + "tens_count": 0, + "participated": false, + "joker": true + }, + { + "tournament": 3, + "score": 314, + "tens_count": 3, + "participated": true + } + ], + "total_score": 589, + "final_score": 0, + "tournaments_participated": 0 + }, + "14": { + "name": "Karli Proje", + "joker_used": true, + "tournament_results": [ + { + "tournament": 1, + "score": 0, + "tens_count": 0, + "participated": false, + "joker": true + }, + { + "tournament": 2, + "score": 0, + "tens_count": 0, + "participated": true + }, + { + "tournament": 3, + "score": 0, + "tens_count": 0, + "participated": true + } + ], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "15": { + "name": "Jan Pleterski", + "joker_used": true, + "tournament_results": [ + { + "tournament": 1, + "score": 269, + "tens_count": 1, + "participated": true + }, + { + "tournament": 2, + "score": 0, + "tens_count": 0, + "participated": false, + "joker": true + }, + { + "tournament": 3, + "score": 267, + "tens_count": 1, + "participated": true + } + ], + "total_score": 536, + "final_score": 0, + "tournaments_participated": 0 + }, + "16": { + "name": "Silvo Poročnik", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 293, + "tens_count": 3, + "participated": true + }, + { + "tournament": 2, + "score": 278, + "tens_count": 3, + "participated": true + }, + { + "tournament": 3, + "score": 282, + "tens_count": 3, + "participated": true + } + ], + "total_score": 853, + "final_score": 0, + "tournaments_participated": 0 + }, + "17": { + "name": "Dušan Onuk", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 313, + "tens_count": 4, + "participated": true + }, + { + "tournament": 2, + "score": 325, + "tens_count": 2, + "participated": true + }, + { + "tournament": 3, + "score": 192, + "tens_count": 3, + "participated": true + } + ], + "total_score": 830, + "final_score": 0, + "tournaments_participated": 0 + }, + "18": { + "name": "Matjaž Pleterski", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 280, + "tens_count": 2, + "participated": true + }, + { + "tournament": 2, + "score": 276, + "tens_count": 4, + "participated": true + }, + { + "tournament": 3, + "score": 307, + "tens_count": 0, + "participated": true + } + ], + "total_score": 863, + "final_score": 0, + "tournaments_participated": 0 + }, + "22": { + "name": "Doris Fesel", + "joker_used": true, + "tournament_results": [ + { + "tournament": 1, + "score": 0, + "tens_count": 0, + "participated": false, + "joker": true + }, + { + "tournament": 2, + "score": 0, + "tens_count": 0, + "participated": true + }, + { + "tournament": 3, + "score": 0, + "tens_count": 0, + "participated": true + } + ], + "total_score": 0, + "final_score": 0, + "tournaments_participated": 0 + }, + "24": { + "name": "Jože Verdinek", + "joker_used": false, + "tournament_results": [ + { + "tournament": 1, + "score": 311, + "tens_count": 5, + "participated": true + }, + { + "tournament": 2, + "score": 315, + "tens_count": 5, + "participated": true + }, + { + "tournament": 3, + "score": 312, + "tens_count": 2, + "participated": true + } + ], + "total_score": 938, + "final_score": 0, + "tournaments_participated": 0 + }, + "50": { + "name": "Vid Ravnjak", + "joker_used": true, + "tournament_results": [ + { + "tournament": 1, + "score": 302, + "tens_count": 6, + "participated": true + }, + { + "tournament": 2, + "score": 309, + "tens_count": 4, + "participated": true + }, + { + "tournament": 3, + "score": 0, + "tens_count": 0, + "participated": false, + "joker": true + } + ], + "total_score": 611, + "final_score": 0, + "tournaments_participated": 0 + }, + "51": { + "name": "Robi Ovčar", + "joker_used": true, + "tournament_results": [ + { + "tournament": 1, + "score": 269, + "tens_count": 1, + "participated": true + }, + { + "tournament": 2, + "score": 0, + "tens_count": 0, + "participated": false, + "joker": true + }, + { + "tournament": 3, + "score": 265, + "tens_count": 1, + "participated": true + } + ], + "total_score": 534, + "final_score": 0, + "tournaments_participated": 0 + } + }, + "completed_tournaments": [ + { + "tournament_number": 1, + "tournament_type": "20_targets", + "finished_at": "2025-11-15T11:50:43.358782", + "results_summary": { + "participants": 15, + "shots_per_participant": 40, + "total_shots": 600, + "format_description": "20 Targets (2 shots each)" + } + }, + { + "tournament_number": 2, + "tournament_type": "20_targets", + "finished_at": "2025-12-13T12:13:29.550924", + "results_summary": { + "participants": 12, + "shots_per_participant": 40, + "total_shots": 480, + "format_description": "20 Targets (2 shots each)" + } + }, + { + "tournament_number": 3, + "tournament_type": "20_targets", + "finished_at": "2026-01-10T11:34:15.800129", + "results_summary": { + "participants": 14, + "shots_per_participant": 40, + "total_shots": 560, + "format_description": "20 Targets (2 shots each)" + } + } + ], + "league_finished": false +} \ No newline at end of file diff --git a/data/players.json b/data/players.json index 682bd6f..2e5e5a9 100644 --- a/data/players.json +++ b/data/players.json @@ -8,17 +8,17 @@ { "id": 2, "name": "Nik Pleterski", - "enabled": true + "enabled": false }, { "id": 3, "name": "Ivan Tandler", - "enabled": true + "enabled": false }, { "id": 4, "name": "Mateja Pleterski", - "enabled": true + "enabled": false }, { "id": 5, @@ -28,7 +28,7 @@ { "id": 6, "name": "Mateja Senica", - "enabled": true + "enabled": false }, { "id": 7, @@ -38,7 +38,7 @@ { "id": 8, "name": "Franc Žigart", - "enabled": true + "enabled": false }, { "id": 9, @@ -58,12 +58,12 @@ { "id": 12, "name": "Matej Kvasnik", - "enabled": true + "enabled": false }, { "id": 13, "name": "Angelca Mrak", - "enabled": true + "enabled": false }, { "id": 14, @@ -93,17 +93,17 @@ { "id": 19, "name": "Franc Rizmal", - "enabled": true + "enabled": false }, { "id": 20, "name": "Jože Preglav", - "enabled": true + "enabled": false }, { "id": 21, "name": "Marko Blimen", - "enabled": true + "enabled": false }, { "id": 22, @@ -113,7 +113,7 @@ { "id": 23, "name": "Robi Krautberger", - "enabled": true + "enabled": false }, { "id": 24, @@ -123,126 +123,136 @@ { "id": 25, "name": "Andrej Herman", - "enabled": true + "enabled": false }, { "id": 26, "name": "Jakob Herman", - "enabled": true + "enabled": false }, { "id": 27, "name": "Janez Mrak", - "enabled": true + "enabled": false }, { "id": 28, "name": "Anže Kolar", - "enabled": true + "enabled": false }, { "id": 29, "name": "Alen Kolar", - "enabled": true + "enabled": false }, { "id": 30, "name": "Maja Hirtl", - "enabled": true + "enabled": false }, { "id": 31, "name": "Dejan Kučnik", - "enabled": true + "enabled": false }, { "id": 32, "name": "David Strniša", - "enabled": true + "enabled": false }, { "id": 33, "name": "Namir Uzunović", - "enabled": true + "enabled": false }, { "id": 34, "name": "Jože Planinšec", - "enabled": true + "enabled": false }, { "id": 35, "name": "Vanja Kolar", - "enabled": true + "enabled": false }, { "id": 36, "name": "Klara Wankmuller", - "enabled": true + "enabled": false }, { "id": 37, "name": "Milan Stramec", - "enabled": true + "enabled": false }, { "id": 38, "name": "Bojan Sudar", - "enabled": true + "enabled": false }, { "id": 39, "name": "Tia Sudar", - "enabled": true + "enabled": false }, { "id": 40, "name": "Jaka Cvar", - "enabled": true + "enabled": false }, { "id": 41, "name": "Tadej Štruc", - "enabled": true + "enabled": false }, { "id": 42, "name": "Jure Glaser", - "enabled": true + "enabled": false }, { "id": 43, "name": "Marko Pokržnik", - "enabled": true + "enabled": false }, { "id": 44, "name": "Anka Kačnik", - "enabled": true + "enabled": false }, { "id": 45, "name": "Lidija Blimen", - "enabled": true + "enabled": false }, { "id": 46, "name": "Tijana Štumpfl", - "enabled": true + "enabled": false }, { "id": 47, "name": "Ljuba Mršak", - "enabled": true + "enabled": false }, { "id": 48, "name": "Janja Salcman", - "enabled": true + "enabled": false }, { "id": 49, "name": "Jolanda Verhnjak", + "enabled": false + }, + { + "id": 50, + "name": "Vid Ravnjak", + "enabled": true + }, + { + "id": 51, + "name": "Robi Ovčar", "enabled": true } ] diff --git a/data/tournament_results.json b/data/tournament_results.json new file mode 100644 index 0000000..8a9718f --- /dev/null +++ b/data/tournament_results.json @@ -0,0 +1,1242 @@ +{ + "tournament_id": "2026-01-10T09:59:15.945867", + "tournament_type": "20_targets", + "participants": { + "1": { + "name": "Domen Pleterski", + "targets": { + "1": { + "shot1": 6, + "shot2": 5 + }, + "2": { + "shot1": 8, + "shot2": 8 + }, + "3": { + "shot1": 9, + "shot2": 6 + }, + "4": { + "shot1": 9, + "shot2": 7 + }, + "5": { + "shot1": 9, + "shot2": 9 + }, + "6": { + "shot1": 10, + "shot2": 8 + }, + "7": { + "shot1": 9, + "shot2": 8 + }, + "8": { + "shot1": 8, + "shot2": 8 + }, + "9": { + "shot1": 9, + "shot2": 8 + }, + "10": { + "shot1": 9, + "shot2": 8 + }, + "11": { + "shot1": 8, + "shot2": 7 + }, + "12": { + "shot1": 10, + "shot2": 9 + }, + "13": { + "shot1": 9, + "shot2": 9 + }, + "14": { + "shot1": 8, + "shot2": 7 + }, + "15": { + "shot1": 8, + "shot2": 8 + }, + "16": { + "shot1": 9, + "shot2": 6 + }, + "17": { + "shot1": 8, + "shot2": 6 + }, + "18": { + "shot1": 7, + "shot2": 8 + }, + "19": { + "shot1": 9, + "shot2": 7 + }, + "20": { + "shot1": 9, + "shot2": 7 + } + }, + "total_score": 320, + "completed": true, + "joker_selected": false + }, + "10": { + "name": "Mitja Čeh", + "targets": { + "1": { + "shot1": 9, + "shot2": 8 + }, + "2": { + "shot1": 9, + "shot2": 7 + }, + "3": { + "shot1": 10, + "shot2": 9 + }, + "4": { + "shot1": 9, + "shot2": 7 + }, + "5": { + "shot1": 8, + "shot2": 8 + }, + "6": { + "shot1": 7, + "shot2": 5 + }, + "7": { + "shot1": 8, + "shot2": 7 + }, + "8": { + "shot1": 8, + "shot2": 5 + }, + "9": { + "shot1": 8, + "shot2": 7 + }, + "10": { + "shot1": 9, + "shot2": 8 + }, + "11": { + "shot1": 10, + "shot2": 8 + }, + "12": { + "shot1": 8, + "shot2": 0 + }, + "13": { + "shot1": 9, + "shot2": 9 + }, + "14": { + "shot1": 8, + "shot2": 8 + }, + "15": { + "shot1": 9, + "shot2": 8 + }, + "16": { + "shot1": 9, + "shot2": 7 + }, + "17": { + "shot1": 7, + "shot2": 5 + }, + "18": { + "shot1": 9, + "shot2": 7 + }, + "19": { + "shot1": 7, + "shot2": 7 + }, + "20": { + "shot1": 9, + "shot2": 7 + } + }, + "total_score": 307, + "completed": true, + "joker_selected": false + }, + "22": { + "name": "Doris Fesel", + "targets": { + "1": { + "shot1": 0, + "shot2": 0 + }, + "2": { + "shot1": 0, + "shot2": 0 + }, + "3": { + "shot1": 0, + "shot2": 0 + }, + "4": { + "shot1": 0, + "shot2": 0 + }, + "5": { + "shot1": 0, + "shot2": 0 + }, + "6": { + "shot1": 0, + "shot2": 0 + }, + "7": { + "shot1": 0, + "shot2": 0 + }, + "8": { + "shot1": 0, + "shot2": 0 + }, + "9": { + "shot1": 0, + "shot2": 0 + }, + "10": { + "shot1": 0, + "shot2": 0 + }, + "11": { + "shot1": 0, + "shot2": 0 + }, + "12": { + "shot1": 0, + "shot2": 0 + }, + "13": { + "shot1": 0, + "shot2": 0 + }, + "14": { + "shot1": 0, + "shot2": 0 + }, + "15": { + "shot1": 0, + "shot2": 0 + }, + "16": { + "shot1": 0, + "shot2": 0 + }, + "17": { + "shot1": 0, + "shot2": 0 + }, + "18": { + "shot1": 0, + "shot2": 0 + }, + "19": { + "shot1": 0, + "shot2": 0 + }, + "20": { + "shot1": 0, + "shot2": 0 + } + }, + "total_score": 0, + "completed": true, + "joker_selected": false + }, + "18": { + "name": "Matjaž Pleterski", + "targets": { + "1": { + "shot1": 9, + "shot2": 7 + }, + "2": { + "shot1": 9, + "shot2": 8 + }, + "3": { + "shot1": 8, + "shot2": 8 + }, + "4": { + "shot1": 9, + "shot2": 9 + }, + "5": { + "shot1": 8, + "shot2": 6 + }, + "6": { + "shot1": 8, + "shot2": 7 + }, + "7": { + "shot1": 9, + "shot2": 8 + }, + "8": { + "shot1": 8, + "shot2": 4 + }, + "9": { + "shot1": 9, + "shot2": 5 + }, + "10": { + "shot1": 8, + "shot2": 4 + }, + "11": { + "shot1": 9, + "shot2": 7 + }, + "12": { + "shot1": 8, + "shot2": 8 + }, + "13": { + "shot1": 8, + "shot2": 7 + }, + "14": { + "shot1": 8, + "shot2": 8 + }, + "15": { + "shot1": 9, + "shot2": 7 + }, + "16": { + "shot1": 8, + "shot2": 8 + }, + "17": { + "shot1": 9, + "shot2": 9 + }, + "18": { + "shot1": 7, + "shot2": 5 + }, + "19": { + "shot1": 8, + "shot2": 7 + }, + "20": { + "shot1": 9, + "shot2": 7 + } + }, + "total_score": 307, + "completed": true, + "joker_selected": false + }, + "17": { + "name": "Dušan Onuk", + "targets": { + "1": { + "shot1": 8, + "shot2": 8 + }, + "2": { + "shot1": 10, + "shot2": 7 + }, + "3": { + "shot1": 9, + "shot2": 8 + }, + "4": { + "shot1": 8, + "shot2": 6 + }, + "5": { + "shot1": 6, + "shot2": 7 + }, + "6": { + "shot1": 9, + "shot2": 8 + }, + "7": { + "shot1": 7, + "shot2": 7 + }, + "8": { + "shot1": 10, + "shot2": 8 + }, + "9": { + "shot1": 9, + "shot2": 7 + }, + "10": { + "shot1": 10, + "shot2": 4 + }, + "11": { + "shot1": 7, + "shot2": 7 + }, + "12": { + "shot1": 9, + "shot2": 1 + }, + "13": { + "shot1": 8, + "shot2": 4 + }, + "14": { + "shot1": 0, + "shot2": 0 + }, + "15": { + "shot1": 0, + "shot2": 0 + }, + "16": { + "shot1": 0, + "shot2": 0 + }, + "17": { + "shot1": 0, + "shot2": 0 + }, + "18": { + "shot1": 0, + "shot2": 0 + }, + "19": { + "shot1": 0, + "shot2": 0 + }, + "20": { + "shot1": 0, + "shot2": 0 + } + }, + "total_score": 192, + "completed": true, + "joker_selected": false + }, + "15": { + "name": "Jan Pleterski", + "targets": { + "1": { + "shot1": 9, + "shot2": 8 + }, + "2": { + "shot1": 7, + "shot2": 5 + }, + "3": { + "shot1": 7, + "shot2": 6 + }, + "4": { + "shot1": 8, + "shot2": 2 + }, + "5": { + "shot1": 7, + "shot2": 4 + }, + "6": { + "shot1": 7, + "shot2": 3 + }, + "7": { + "shot1": 6, + "shot2": 6 + }, + "8": { + "shot1": 8, + "shot2": 6 + }, + "9": { + "shot1": 8, + "shot2": 1 + }, + "10": { + "shot1": 9, + "shot2": 9 + }, + "11": { + "shot1": 8, + "shot2": 6 + }, + "12": { + "shot1": 9, + "shot2": 7 + }, + "13": { + "shot1": 9, + "shot2": 3 + }, + "14": { + "shot1": 9, + "shot2": 3 + }, + "15": { + "shot1": 8, + "shot2": 7 + }, + "16": { + "shot1": 7, + "shot2": 4 + }, + "17": { + "shot1": 10, + "shot2": 6 + }, + "18": { + "shot1": 8, + "shot2": 7 + }, + "19": { + "shot1": 8, + "shot2": 8 + }, + "20": { + "shot1": 9, + "shot2": 5 + } + }, + "total_score": 267, + "completed": true, + "joker_selected": false + }, + "51": { + "name": "Robi Ovčar", + "targets": { + "1": { + "shot1": 9, + "shot2": 7 + }, + "2": { + "shot1": 9, + "shot2": 4 + }, + "3": { + "shot1": 4, + "shot2": 1 + }, + "4": { + "shot1": 7, + "shot2": 6 + }, + "5": { + "shot1": 7, + "shot2": 6 + }, + "6": { + "shot1": 6, + "shot2": 2 + }, + "7": { + "shot1": 9, + "shot2": 5 + }, + "8": { + "shot1": 9, + "shot2": 8 + }, + "9": { + "shot1": 4, + "shot2": 4 + }, + "10": { + "shot1": 9, + "shot2": 9 + }, + "11": { + "shot1": 8, + "shot2": 6 + }, + "12": { + "shot1": 8, + "shot2": 8 + }, + "13": { + "shot1": 8, + "shot2": 4 + }, + "14": { + "shot1": 10, + "shot2": 8 + }, + "15": { + "shot1": 4, + "shot2": 2 + }, + "16": { + "shot1": 9, + "shot2": 7 + }, + "17": { + "shot1": 9, + "shot2": 6 + }, + "18": { + "shot1": 7, + "shot2": 7 + }, + "19": { + "shot1": 7, + "shot2": 7 + }, + "20": { + "shot1": 8, + "shot2": 7 + } + }, + "total_score": 265, + "completed": true, + "joker_selected": false + }, + "16": { + "name": "Silvo Poročnik", + "targets": { + "1": { + "shot1": 8, + "shot2": 1 + }, + "2": { + "shot1": 7, + "shot2": 7 + }, + "3": { + "shot1": 8, + "shot2": 8 + }, + "4": { + "shot1": 8, + "shot2": 7 + }, + "5": { + "shot1": 9, + "shot2": 6 + }, + "6": { + "shot1": 9, + "shot2": 5 + }, + "7": { + "shot1": 10, + "shot2": 8 + }, + "8": { + "shot1": 10, + "shot2": 7 + }, + "9": { + "shot1": 7, + "shot2": 6 + }, + "10": { + "shot1": 8, + "shot2": 7 + }, + "11": { + "shot1": 8, + "shot2": 4 + }, + "12": { + "shot1": 7, + "shot2": 4 + }, + "13": { + "shot1": 8, + "shot2": 5 + }, + "14": { + "shot1": 8, + "shot2": 8 + }, + "15": { + "shot1": 8, + "shot2": 6 + }, + "16": { + "shot1": 7, + "shot2": 2 + }, + "17": { + "shot1": 8, + "shot2": 6 + }, + "18": { + "shot1": 7, + "shot2": 7 + }, + "19": { + "shot1": 10, + "shot2": 9 + }, + "20": { + "shot1": 8, + "shot2": 6 + } + }, + "total_score": 282, + "completed": true, + "joker_selected": false + }, + "7": { + "name": "Branko Pokeržnik", + "targets": { + "1": { + "shot1": 9, + "shot2": 8 + }, + "2": { + "shot1": 8, + "shot2": 6 + }, + "3": { + "shot1": 9, + "shot2": 7 + }, + "4": { + "shot1": 9, + "shot2": 7 + }, + "5": { + "shot1": 9, + "shot2": 7 + }, + "6": { + "shot1": 8, + "shot2": 7 + }, + "7": { + "shot1": 7, + "shot2": 6 + }, + "8": { + "shot1": 9, + "shot2": 8 + }, + "9": { + "shot1": 8, + "shot2": 8 + }, + "10": { + "shot1": 8, + "shot2": 8 + }, + "11": { + "shot1": 8, + "shot2": 6 + }, + "12": { + "shot1": 7, + "shot2": 6 + }, + "13": { + "shot1": 10, + "shot2": 8 + }, + "14": { + "shot1": 9, + "shot2": 9 + }, + "15": { + "shot1": 8, + "shot2": 8 + }, + "16": { + "shot1": 9, + "shot2": 8 + }, + "17": { + "shot1": 9, + "shot2": 8 + }, + "18": { + "shot1": 9, + "shot2": 7 + }, + "19": { + "shot1": 9, + "shot2": 8 + }, + "20": { + "shot1": 8, + "shot2": 8 + } + }, + "total_score": 318, + "completed": true, + "joker_selected": false + }, + "9": { + "name": "Janez Božič", + "targets": { + "1": { + "shot1": 8, + "shot2": 6 + }, + "2": { + "shot1": 8, + "shot2": 4 + }, + "3": { + "shot1": 9, + "shot2": 6 + }, + "4": { + "shot1": 8, + "shot2": 8 + }, + "5": { + "shot1": 10, + "shot2": 8 + }, + "6": { + "shot1": 9, + "shot2": 7 + }, + "7": { + "shot1": 9, + "shot2": 8 + }, + "8": { + "shot1": 9, + "shot2": 8 + }, + "9": { + "shot1": 5, + "shot2": 5 + }, + "10": { + "shot1": 7, + "shot2": 5 + }, + "11": { + "shot1": 7, + "shot2": 6 + }, + "12": { + "shot1": 9, + "shot2": 6 + }, + "13": { + "shot1": 7, + "shot2": 6 + }, + "14": { + "shot1": 6, + "shot2": 2 + }, + "15": { + "shot1": 7, + "shot2": 4 + }, + "16": { + "shot1": 7, + "shot2": 3 + }, + "17": { + "shot1": 7, + "shot2": 5 + }, + "18": { + "shot1": 5, + "shot2": 3 + }, + "19": { + "shot1": 2, + "shot2": 4 + }, + "20": { + "shot1": 9, + "shot2": 9 + } + }, + "total_score": 261, + "completed": true, + "joker_selected": false + }, + "5": { + "name": "Jože Verhnjak", + "targets": { + "1": { + "shot1": 9, + "shot2": 8 + }, + "2": { + "shot1": 8, + "shot2": 2 + }, + "3": { + "shot1": 7, + "shot2": 8 + }, + "4": { + "shot1": 9, + "shot2": 6 + }, + "5": { + "shot1": 7, + "shot2": 6 + }, + "6": { + "shot1": 8, + "shot2": 7 + }, + "7": { + "shot1": 9, + "shot2": 7 + }, + "8": { + "shot1": 8, + "shot2": 7 + }, + "9": { + "shot1": 9, + "shot2": 6 + }, + "10": { + "shot1": 8, + "shot2": 2 + }, + "11": { + "shot1": 9, + "shot2": 6 + }, + "12": { + "shot1": 8, + "shot2": 8 + }, + "13": { + "shot1": 8, + "shot2": 4 + }, + "14": { + "shot1": 8, + "shot2": 6 + }, + "15": { + "shot1": 9, + "shot2": 7 + }, + "16": { + "shot1": 10, + "shot2": 8 + }, + "17": { + "shot1": 10, + "shot2": 7 + }, + "18": { + "shot1": 8, + "shot2": 8 + }, + "19": { + "shot1": 9, + "shot2": 8 + }, + "20": { + "shot1": 6, + "shot2": 5 + } + }, + "total_score": 293, + "completed": true, + "joker_selected": false + }, + "24": { + "name": "Jože Verdinek", + "targets": { + "1": { + "shot1": 9, + "shot2": 8 + }, + "2": { + "shot1": 9, + "shot2": 6 + }, + "3": { + "shot1": 9, + "shot2": 9 + }, + "4": { + "shot1": 8, + "shot2": 6 + }, + "5": { + "shot1": 8, + "shot2": 7 + }, + "6": { + "shot1": 8, + "shot2": 7 + }, + "7": { + "shot1": 8, + "shot2": 8 + }, + "8": { + "shot1": 9, + "shot2": 8 + }, + "9": { + "shot1": 9, + "shot2": 8 + }, + "10": { + "shot1": 8, + "shot2": 6 + }, + "11": { + "shot1": 9, + "shot2": 6 + }, + "12": { + "shot1": 8, + "shot2": 5 + }, + "13": { + "shot1": 9, + "shot2": 8 + }, + "14": { + "shot1": 9, + "shot2": 7 + }, + "15": { + "shot1": 6, + "shot2": 5 + }, + "16": { + "shot1": 9, + "shot2": 7 + }, + "17": { + "shot1": 10, + "shot2": 8 + }, + "18": { + "shot1": 10, + "shot2": 7 + }, + "19": { + "shot1": 9, + "shot2": 7 + }, + "20": { + "shot1": 9, + "shot2": 6 + } + }, + "total_score": 312, + "completed": true, + "joker_selected": false + }, + "14": { + "name": "Karli Proje", + "targets": { + "1": { + "shot1": 0, + "shot2": 0 + }, + "2": { + "shot1": 0, + "shot2": 0 + }, + "3": { + "shot1": 0, + "shot2": 0 + }, + "4": { + "shot1": 0, + "shot2": 0 + }, + "5": { + "shot1": 0, + "shot2": 0 + }, + "6": { + "shot1": 0, + "shot2": 0 + }, + "7": { + "shot1": 0, + "shot2": 0 + }, + "8": { + "shot1": 0, + "shot2": 0 + }, + "9": { + "shot1": 0, + "shot2": 0 + }, + "10": { + "shot1": 0, + "shot2": 0 + }, + "11": { + "shot1": 0, + "shot2": 0 + }, + "12": { + "shot1": 0, + "shot2": 0 + }, + "13": { + "shot1": 0, + "shot2": 0 + }, + "14": { + "shot1": 0, + "shot2": 0 + }, + "15": { + "shot1": 0, + "shot2": 0 + }, + "16": { + "shot1": 0, + "shot2": 0 + }, + "17": { + "shot1": 0, + "shot2": 0 + }, + "18": { + "shot1": 0, + "shot2": 0 + }, + "19": { + "shot1": 0, + "shot2": 0 + }, + "20": { + "shot1": 0, + "shot2": 0 + } + }, + "total_score": 0, + "completed": true, + "joker_selected": false + }, + "11": { + "name": "Rado Kefer", + "targets": { + "1": { + "shot1": 8, + "shot2": 7 + }, + "2": { + "shot1": 9, + "shot2": 6 + }, + "3": { + "shot1": 9, + "shot2": 7 + }, + "4": { + "shot1": 8, + "shot2": 8 + }, + "5": { + "shot1": 10, + "shot2": 8 + }, + "6": { + "shot1": 8, + "shot2": 8 + }, + "7": { + "shot1": 9, + "shot2": 7 + }, + "8": { + "shot1": 10, + "shot2": 8 + }, + "9": { + "shot1": 7, + "shot2": 6 + }, + "10": { + "shot1": 8, + "shot2": 7 + }, + "11": { + "shot1": 8, + "shot2": 6 + }, + "12": { + "shot1": 7, + "shot2": 4 + }, + "13": { + "shot1": 9, + "shot2": 9 + }, + "14": { + "shot1": 9, + "shot2": 8 + }, + "15": { + "shot1": 10, + "shot2": 8 + }, + "16": { + "shot1": 9, + "shot2": 7 + }, + "17": { + "shot1": 8, + "shot2": 8 + }, + "18": { + "shot1": 7, + "shot2": 8 + }, + "19": { + "shot1": 8, + "shot2": 8 + }, + "20": { + "shot1": 8, + "shot2": 7 + } + }, + "total_score": 314, + "completed": true, + "joker_selected": false + } + }, + "tournament_finished": true, + "created_at": "2026-01-10T09:59:15.947146", + "league_tournament_number": 3, + "finished_at": "2026-01-10T11:34:15.800055" +} \ No newline at end of file diff --git a/locales/en.json b/locales/en.json index b66bcb5..656e35b 100644 --- a/locales/en.json +++ b/locales/en.json @@ -95,7 +95,8 @@ "created": "Created", "tournaments": "Tournaments", "league_tournament": "League Tournament", - "finished": "Finished" + "finished": "Finished", + "view_current_results": "View Current Standings" }, "tournament_types": { "4_targets": "4 Targets", diff --git a/locales/sl.json b/locales/sl.json index b1fa566..34db67b 100644 --- a/locales/sl.json +++ b/locales/sl.json @@ -96,7 +96,8 @@ "league_completed": "Liga Zaključena", "section_title": "Sekcija", "finished": "Zaključeno", - "targets": "Število Tarč" + "targets": "Število Tarč", + "view_current_results": "Oglej Trenutne Rezultate" }, "tournament_types": { "4_targets": "4 Tarče", diff --git a/templates/league_scoreboard_display.html b/templates/league_scoreboard_display.html index f7661bf..9a8717e 100644 --- a/templates/league_scoreboard_display.html +++ b/templates/league_scoreboard_display.html @@ -1100,6 +1100,8 @@ tournamentCells += '🃏'; } else { const score = result.score; + const tensCount = result.tens_count || 0; + // Check if this specific tournament index should be excluded const isExcluded = best4Logic.excludedIndices.includes(participatedTournamentIndex) && best4Logic.allScores.length > 4; const scoreClass = isExcluded ? 'excluded' : 'counted'; diff --git a/templates/results_calculator.html b/templates/results_calculator.html index 9ce4b1d..8418ed7 100644 --- a/templates/results_calculator.html +++ b/templates/results_calculator.html @@ -236,6 +236,44 @@ font-weight: bold; } + .joker-checkbox-wrapper { + margin-top: 8px; + } + + .joker-label { + display: flex; + align-items: center; + gap: 8px; + cursor: pointer; + padding: 6px 12px; + background: #fff3cd; + border: 2px solid #ffc107; + border-radius: 8px; + transition: all 0.2s ease; + } + + .joker-label:hover { + background: #ffc107; + border-color: #ff9800; + } + + .joker-checkbox-calc { + cursor: pointer; + width: 18px; + height: 18px; + } + + .joker-checkbox-calc:disabled { + cursor: not-allowed; + opacity: 0.6; + } + + .joker-text { + font-size: 0.9rem; + font-weight: 600; + color: #856404; + } + .participant-status { display: flex; align-items: center; @@ -670,6 +708,19 @@
{{ participant.name }}
ID: {{ player_id }}
+ {% if league_state %} +
+ +
+ {% endif %}
@@ -1338,6 +1389,49 @@ } } + // Handle joker checkbox change + function handleJokerChange(playerId) { + const checkbox = document.getElementById(`joker-calc-${playerId}`); + const isChecked = checkbox.checked; + + // If checked, warn user that all scores will be set to zero + if (isChecked) { + if (confirm('Marking this player as using their Joker will set all their scores to zero. Continue?')) { + // Set all scores to zero + const participant = results.participants[playerId]; + if (participant && participant.targets) { + Object.keys(participant.targets).forEach(targetId => { + const target = participant.targets[targetId]; + Object.keys(target).forEach(shotKey => { + if (shotKey.startsWith('shot')) { + target[shotKey] = 0; + } + }); + }); + } + participant.total_score = 0; + participant.completed = true; // Mark as completed since joker is used + participant.joker_selected = true; // Mark joker as selected in results + + // Update UI + updateParticipantTotal(playerId); + updateParticipantStatus(playerId); + updateParticipantTens(playerId); + updateOverallProgress(); + updateOverallTens(); + + // Disable the checkbox so it can't be unchecked + checkbox.disabled = true; + + // Save the change + savePlayerData(playerId); + } else { + // User cancelled, uncheck the box + checkbox.checked = false; + } + } + } + // Keyboard shortcuts document.addEventListener('keydown', function(event) { if (event.ctrlKey && event.key === 's') { @@ -1352,7 +1446,7 @@ // Initialize targets for all players Object.keys(results.participants).forEach(playerId => { initializeTargetsForPlayer(parseInt(playerId)); - + // Update initial states for (let i = 1; i <= numTargets; i++) { updateTargetGroupStyling(parseInt(playerId), i); @@ -1360,7 +1454,22 @@ updateParticipantStatus(parseInt(playerId)); updateParticipantTens(parseInt(playerId)); }); - + + // Initialize joker checkboxes for league tournaments + {% if league_state %} + const leagueState = {{ league_state | tojson | safe }}; + Object.keys(results.participants).forEach(playerId => { + const checkbox = document.getElementById(`joker-calc-${playerId}`); + if (checkbox && leagueState.participants && leagueState.participants[playerId]) { + const participant = leagueState.participants[playerId]; + if (participant.joker_used) { + checkbox.checked = true; + checkbox.disabled = true; + } + } + }); + {% endif %} + updateOverallProgress(); updateOverallTens(); diff --git a/templates/tournament.html b/templates/tournament.html index b26dfce..3a85028 100644 --- a/templates/tournament.html +++ b/templates/tournament.html @@ -767,14 +767,22 @@ font-weight: bold; transition: all 0.2s ease; min-width: 200px; - text-decoration: none; + text-decoration: none !important; font-family: Arial, sans-serif; + display: inline-block; } .action-btn:hover { background: #1e7e34; transform: translateY(-1px); box-shadow: 0 4px 8px rgba(0, 123, 255, 0.3); + text-decoration: none !important; + } + + .action-btn:focus, + .action-btn:active, + .action-btn:visited { + text-decoration: none !important; } .action-btn:disabled { @@ -800,6 +808,16 @@ background: #c82333; } + .action-btn.info { + background: #007bff; + border-color: #0056b3; + } + + .action-btn.info:hover { + background: #0056b3; + box-shadow: 0 4px 8px rgba(0, 123, 255, 0.4); + } + .player-count { margin: 20px 0; font-size: 1.2rem; @@ -1606,7 +1624,7 @@
{% endfor %} @@ -1614,6 +1632,11 @@
+ {% if league_state.completed_tournaments|length > 0 %} + + 📊 View Current Standings + + {% endif %} diff --git a/tv_app.py b/tv_app.py index ebc29c7..e80f59c 100644 --- a/tv_app.py +++ b/tv_app.py @@ -1,6 +1,5 @@ """ TV_APP V1.0.0 - Tournament and League Management System -Flask web application for managing tournaments with multi-camera streaming """ from flask import Flask, render_template, request, redirect, jsonify, session @@ -1594,6 +1593,8 @@ def finish_tournament(): 'participated': False, 'joker': True }) + # Mark joker as used for this player + participant['joker_used'] = True # Calculate total shots correctly for any tournament type tournament_type = results.get('tournament_type', '20_targets')