Files
Sdk_TV_app/TV_APP_V2/templates/tournament.html
T
2025-07-30 17:53:24 +02:00

1565 lines
42 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Tournament Management</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* {
box-sizing: border-box;
}
html, body {
margin: 0;
padding: 0;
background: #f5f5f5;
font-family: Arial, sans-serif;
min-height: 100vh;
}
.navbar {
background: white;
color: black;
padding: 15px 25px;
display: flex;
align-items: center;
justify-content: space-between;
border-bottom: 2px solid #ccc;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.navbar-title {
font-size: 1.8rem;
font-weight: bold;
color: #333;
}
.navbar-controls {
display: flex;
gap: 12px;
align-items: center;
}
.nav-btn {
background: #f8f9fa;
border: 2px solid #e9ecef;
cursor: pointer;
padding: 12px 20px;
border-radius: 8px;
transition: all 0.2s ease;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
color: #333;
text-decoration: none;
font-weight: bold;
font-size: 0.9rem;
}
.nav-btn:hover {
background: #e9ecef;
border-color: #007bff;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
transform: translateY(-1px);
color: #007bff;
}
.nav-btn.primary {
background: #007bff;
border-color: #0056b3;
color: white;
}
.nav-btn.primary:hover {
background: #0056b3;
color: white;
}
.nav-btn.success {
background: #28a745;
border-color: #1e7e34;
color: white;
}
.nav-btn.success:hover {
background: #1e7e34;
color: white;
}
.nav-btn.danger {
background: #dc3545;
border-color: #c82333;
color: white;
}
.nav-btn.danger:hover {
background: #c82333;
color: white;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 30px 20px;
}
.section {
background: white;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
padding: 25px;
margin-bottom: 30px;
}
.section-title {
font-size: 1.5rem;
font-weight: bold;
color: #333;
margin-bottom: 20px;
border-bottom: 3px solid #007bff;
padding-bottom: 8px;
}
/* League Status */
.league-status {
text-align: center;
padding: 30px;
}
.league-active {
color: #28a745;
font-size: 1.4rem;
font-weight: bold;
margin-bottom: 20px;
}
.league-inactive {
color: #6c757d;
font-size: 1.2rem;
margin-bottom: 20px;
}
.league-info {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 20px;
margin: 20px 0;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 15px;
}
.info-item {
background: white;
padding: 15px;
border-radius: 6px;
border: 1px solid #e9ecef;
text-align: center;
}
.info-label {
font-size: 0.9rem;
color: #666;
margin-bottom: 5px;
}
.info-value {
font-size: 1.3rem;
font-weight: bold;
color: #333;
}
/* Tournament Type Selection */
.tournament-type-selection {
background: #f8f9fa;
border: 2px solid #e9ecef;
border-radius: 8px;
padding: 20px;
margin: 20px 0;
}
.type-title {
font-size: 1.1rem;
font-weight: bold;
color: #333;
margin-bottom: 15px;
}
.type-options {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 15px;
}
.type-option {
background: white;
border: 2px solid #dee2e6;
border-radius: 8px;
padding: 20px;
cursor: pointer;
transition: all 0.2s ease;
text-align: center;
}
.type-option:hover {
border-color: #007bff;
box-shadow: 0 2px 8px rgba(0, 123, 255, 0.15);
}
.type-option.selected {
border-color: #007bff;
background: #f0f8ff;
}
.type-option input[type="radio"] {
margin-right: 10px;
width: 18px;
height: 18px;
}
.type-name {
font-size: 1.2rem;
font-weight: bold;
color: #333;
margin-bottom: 8px;
}
.type-description {
font-size: 0.9rem;
color: #666;
line-height: 1.4;
}
/* Joker Management */
.joker-section {
background: #fff3cd;
border: 2px solid #ffeaa7;
border-radius: 8px;
padding: 20px;
margin: 20px 0;
}
.joker-title {
font-size: 1.1rem;
font-weight: bold;
color: #856404;
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 8px;
}
.joker-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
}
.joker-player {
background: white;
border: 1px solid #dee2e6;
border-radius: 6px;
padding: 15px;
display: flex;
align-items: center;
justify-content: space-between;
}
.joker-player.used {
opacity: 0.6;
background: #f8f9fa;
}
.joker-checkbox {
width: 18px;
height: 18px;
}
.joker-checkbox:disabled {
cursor: not-allowed;
}
/* Action Buttons */
.action-buttons {
display: flex;
gap: 15px;
flex-wrap: wrap;
justify-content: center;
margin: 20px 0;
}
.action-btn {
background: #007bff;
border: none;
color: white;
padding: 15px 30px;
border-radius: 8px;
cursor: pointer;
font-size: 1.1rem;
font-weight: bold;
transition: all 0.2s ease;
min-width: 200px;
text-decoration: none;
}
.action-btn:hover {
background: #0056b3;
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(0, 123, 255, 0.3);
}
.action-btn:disabled {
background: #6c757d;
cursor: not-allowed;
transform: none;
box-shadow: none;
}
.action-btn.success {
background: #28a745;
}
.action-btn.success:hover {
background: #1e7e34;
}
.action-btn.danger {
background: #dc3545;
}
.action-btn.danger:hover {
background: #c82333;
}
.player-count {
margin: 20px 0;
font-size: 1.2rem;
color: #333;
text-align: center;
}
.enabled-count {
color: #28a745;
font-weight: bold;
font-size: 1.4rem;
}
.warning {
background: #fff3cd;
border: 1px solid #ffeaa7;
color: #856404;
padding: 12px 16px;
border-radius: 6px;
margin: 15px 0;
font-weight: 500;
}
/* Tournament Status */
.tournament-status {
text-align: center;
padding: 30px;
}
.tournament-active {
color: #28a745;
font-size: 1.4rem;
font-weight: bold;
margin-bottom: 20px;
}
.tournament-inactive {
color: #6c757d;
font-size: 1.2rem;
margin-bottom: 20px;
}
.tournament-info {
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
padding: 20px;
margin: 20px 0;
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
}
/* Add Player Form */
.add-player-form {
display: flex;
gap: 15px;
align-items: center;
padding: 20px;
background: #f8f9fa;
border: 1px solid #dee2e6;
border-radius: 8px;
margin-bottom: 25px;
}
.add-player-form input {
flex: 1;
padding: 12px 15px;
border: 1px solid #ced4da;
border-radius: 6px;
font-size: 1rem;
transition: border-color 0.2s ease;
}
.add-player-form input:focus {
outline: none;
border-color: #007bff;
}
.add-btn {
background: #28a745;
border: none;
color: white;
padding: 12px 20px;
border-radius: 6px;
cursor: pointer;
font-weight: bold;
font-size: 1rem;
transition: background-color 0.2s ease;
white-space: nowrap;
}
.add-btn:hover {
background: #218838;
}
.add-btn:disabled {
background: #6c757d;
cursor: not-allowed;
}
.uncheck-all-btn {
background: #dc3545;
border: none;
color: white;
padding: 12px 20px;
border-radius: 6px;
cursor: pointer;
font-weight: bold;
font-size: 1rem;
transition: background-color 0.2s ease;
white-space: nowrap;
}
.uncheck-all-btn:hover {
background: #c82333;
}
.uncheck-all-btn:disabled {
background: #6c757d;
cursor: not-allowed;
}
/* Player Cards */
.player-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));
gap: 20px;
margin-top: 20px;
}
.player-card {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
border: 1px solid #dee2e6;
transition: box-shadow 0.2s ease;
position: relative;
}
.player-card.competing {
border-color: #28a745;
background: #f8fff9;
}
.player-card.not-competing {
border-color: #dc3545;
background: #fff5f5;
opacity: 0.8;
}
.player-card:hover {
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
}
.player-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 15px;
}
.player-id-badge {
background: #007bff;
color: white;
padding: 6px 12px;
border-radius: 15px;
font-size: 0.9rem;
font-weight: bold;
min-width: 40px;
text-align: center;
}
.player-status {
font-size: 0.9rem;
font-weight: bold;
padding: 4px 10px;
border-radius: 12px;
}
.status-competing {
background: #d4edda;
color: #155724;
}
.status-not-competing {
background: #f8d7da;
color: #721c24;
}
.player-name-section {
margin: 15px 0;
}
.player-name {
font-size: 1.2rem;
font-weight: 600;
color: #333;
margin-bottom: 5px;
}
.player-name input {
width: 100%;
padding: 8px 12px;
border: 1px solid #ced4da;
border-radius: 6px;
font-size: 1.1rem;
font-weight: 600;
background: white;
transition: border-color 0.2s ease;
}
.player-name input:focus {
outline: none;
border-color: #007bff;
}
.player-controls {
display: flex;
align-items: center;
justify-content: space-between;
gap: 15px;
margin-top: 20px;
padding-top: 15px;
border-top: 1px solid #e9ecef;
}
.competing-toggle {
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
padding: 5px 10px;
border-radius: 6px;
transition: background-color 0.2s ease;
}
.competing-toggle:hover {
background: #f8f9fa;
}
.toggle-checkbox {
width: 18px;
height: 18px;
cursor: pointer;
accent-color: #28a745;
}
.toggle-label {
font-weight: 500;
color: #333;
cursor: pointer;
}
.card-buttons {
display: flex;
gap: 8px;
}
.edit-btn {
background: #ffc107;
border: none;
color: white;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
font-weight: 500;
transition: background-color 0.2s ease;
}
.edit-btn:hover {
background: #e0a800;
}
.delete-btn {
background: #dc3545;
border: none;
color: white;
padding: 6px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 0.9rem;
font-weight: 500;
transition: background-color 0.2s ease;
}
.delete-btn:hover {
background: #c82333;
}
/* Confirmation Modal */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
opacity: 0;
visibility: hidden;
transition: all 0.2s ease;
}
.modal-overlay.active {
opacity: 1;
visibility: visible;
}
.confirmation-modal {
background: white;
border-radius: 8px;
padding: 25px;
max-width: 500px;
width: 90%;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
transform: scale(0.9);
transition: transform 0.2s ease;
}
.modal-overlay.active .confirmation-modal {
transform: scale(1);
}
.modal-title {
font-size: 1.2rem;
font-weight: bold;
color: #333;
margin-bottom: 12px;
}
.modal-message {
color: #666;
margin-bottom: 20px;
line-height: 1.4;
}
.modal-buttons {
display: flex;
gap: 12px;
justify-content: flex-end;
}
.modal-btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
transition: background-color 0.2s ease;
}
.modal-btn.cancel {
background: #6c757d;
color: white;
}
.modal-btn.cancel:hover {
background: #5a6268;
}
.modal-btn.confirm {
background: #dc3545;
color: white;
}
.modal-btn.confirm:hover {
background: #c82333;
}
/* Mobile responsive */
@media (max-width: 768px) {
.navbar {
padding: 12px 20px;
flex-direction: column;
gap: 15px;
}
.navbar-controls {
flex-wrap: wrap;
justify-content: center;
}
.container {
padding: 20px 15px;
}
.player-grid {
grid-template-columns: 1fr;
}
.add-player-form {
flex-direction: column;
align-items: stretch;
gap: 12px;
}
.tournament-info, .league-info {
grid-template-columns: 1fr;
}
.type-options {
grid-template-columns: 1fr;
}
.action-buttons {
flex-direction: column;
align-items: center;
}
.action-btn {
min-width: 250px;
}
.player-controls {
flex-direction: column;
align-items: stretch;
gap: 10px;
}
.card-buttons {
justify-content: center;
}
}
</style>
</head>
<body>
<div class="navbar">
<div class="navbar-title">🏆 Tournament Management</div>
<div class="navbar-controls">
<a href="/" class="nav-btn">← Dashboard</a>
{% if league_state and not league_state.league_finished %}
<a href="/results/calculator" class="nav-btn primary">🎯 Results Calculator</a>
<button class="nav-btn danger" onclick="resetLeague()">🗑️ Reset League</button>
{% elif tournament_state %}
<a href="/tournament/draft" class="nav-btn">📋 View Draft</a>
<a href="/results/calculator" class="nav-btn primary">🎯 Results Calculator</a>
<button class="nav-btn danger" onclick="resetTournament()">🗑️ Reset Tournament</button>
{% endif %}
</div>
</div>
<div class="container">
<!-- League Status Section -->
<!-- League or Tournament Management Section -->
<div class="section">
{% if league_state and not league_state.league_finished %}
<h2 class="section-title">🏆 League Management</h2>
<div class="league-status">
<div class="league-active">🏆 League Active</div>
<div class="league-info">
<div class="info-item">
<div class="info-label">Tournament Type</div>
<div class="info-value">
{% if league_state.tournament_type == '40_targets' %}
40 Targets
{% else %}
20 Targets
{% endif %}
</div>
</div>
<div class="info-item">
<div class="info-label">Current Tournament</div>
<div class="info-value">{{ league_state.current_tournament }} / {{ league_state.total_tournaments }}</div>
</div>
<div class="info-item">
<div class="info-label">Participants</div>
<div class="info-value">{{ league_state.participants|length }}</div>
</div>
<div class="info-item">
<div class="info-label">Completed</div>
<div class="info-value">{{ league_state.completed_tournaments|length }}</div>
</div>
<div class="info-item">
<div class="info-label">Created</div>
<div class="info-value">{{ league_state.created_at[:10] }}</div>
</div>
</div>
{% if tournament_state %}
{% else %}
{% if league_state.current_tournament < league_state.total_tournaments %}
<!-- Joker selection -->
<div class="joker-section" id="jokerSection">
<div class="joker-title">🃏 Joker Selection for Tournament {{ league_state.current_tournament + 1 }}</div>
<p style="margin-bottom: 15px; color: #856404;">Select players who will use their joker (skip this tournament). Each player can only use their joker once per league.</p>
<div class="joker-grid">
{% for player_id, participant in league_state.participants.items() %}
<div class="joker-player {% if participant.joker_used %}used{% endif %}">
<span>{{ participant.name }}</span>
<input type="checkbox"
class="joker-checkbox"
id="joker_{{ player_id }}"
{% if participant.joker_used %}disabled{% endif %}
data-player-id="{{ player_id }}">
</div>
{% endfor %}
</div>
</div>
<div class="action-buttons">
<button class="action-btn success" onclick="startNextTournament()">
🚀 Start Tournament {{ league_state.current_tournament + 1 }}
</button>
</div>
{% else %}
<div class="warning">
<strong>League Complete!</strong> All tournaments scheduled. Finish current one to see final results.
</div>
{% endif %}
{% endif %}
</div>
{% elif league_state and league_state.league_finished %}
<h2 class="section-title">🏁 League Completed</h2>
<div class="league-status">
<div class="league-active">🏆 League Completed!</div>
<div class="league-info">
<div class="info-item">
<div class="info-label">Tournament Type</div>
<div class="info-value">{{ league_state.tournament_type == '40_targets' and '40 Targets' or '20 Targets' }}</div>
</div>
<div class="info-item">
<div class="info-label">Participants</div>
<div class="info-value">{{ league_state.participants|length }}</div>
</div>
<div class="info-item">
<div class="info-label">Tournaments</div>
<div class="info-value">{{ league_state.total_tournaments }}</div>
</div>
<div class="info-item">
<div class="info-label">Finished</div>
<div class="info-value">{{ league_state.finished_at[:10] if league_state.finished_at else 'Today' }}</div>
</div>
</div>
<div class="action-buttons">
<a href="/results" class="action-btn success">🏆 View League Results</a>
<button class="action-btn danger" onclick="resetLeague()">🗑️ Reset League</button>
</div>
</div>
{% elif not league_state and tournament_state %}
<h2 class="section-title">🎯 Single Tournament Management</h2>
<div class="tournament-status">
<div class="tournament-active">🎯 Tournament Active</div>
<div class="league-info">
<div class="info-item">
<div class="info-label">Tournament Type</div>
<div class="info-value">{{ tournament_state.tournament_type == '40_targets' and '40 Targets' or '20 Targets' }}</div>
</div>
<div class="info-item">
<div class="info-label">Players</div>
<div class="info-value">{{ tournament_state.total_players }}</div>
</div>
<div class="info-item">
<div class="info-label">Rounds</div>
<div class="info-value">{{ tournament_state.total_rounds }}</div>
</div>
<div class="info-item">
<div class="info-label">Current Round</div>
<div class="info-value">{{ tournament_state.current_round }}</div>
</div>
<div class="info-item">
<div class="info-label">Created</div>
<div class="info-value">{{ tournament_state.created_at[:10] if tournament_state.created_at else 'Today' }}</div>
</div>
</div>
<div class="action-buttons">
<a href="/tournament/draft" class="action-btn">📋 View Draft</a>
<a href="/results/calculator" class="action-btn success">🎯 Score Tournament</a>
<a href="/" class="action-btn">📺 Dashboard</a>
<button class="action-btn danger" onclick="resetTournament()">🗑️ Reset Tournament</button>
</div>
</div>
{% else %}
<h2 class="section-title">🏁 Setup</h2>
<div class="league-inactive">No Active League or Tournament</div>
<!-- Tournament Type Selection -->
<div class="tournament-type-selection">
<div class="type-title">🎯 Select Tournament Type</div>
<div class="type-options">
<div class="type-option selected" onclick="selectTournamentType('20_targets')">
<input type="radio" name="tournament_type" value="20_targets" checked>
<div class="type-name">20 Targets</div>
<div class="type-description">Standard format with 20 targets, 2 shots each (40 shots total)</div>
</div>
<div class="type-option" onclick="selectTournamentType('40_targets')">
<input type="radio" name="tournament_type" value="40_targets">
<div class="type-name">40 Targets</div>
<div class="type-description">Extended format with 40 targets, 2 shots each (80 shots total)</div>
</div>
</div>
</div>
<div class="player-count">
<span class="enabled-count" id="enabledCount">0</span> players enabled
</div>
<div class="action-buttons">
<button class="action-btn success" id="startLeagueBtn" onclick="startLeague()">🏆 Start League (6 Tournaments)</button>
<button class="action-btn" id="startSingleBtn" onclick="startSingleTournament()">🎯 Start Single Tournament</button>
</div>
<div class="warning" id="warningMessage" style="display: none;">
<strong>Note:</strong> You need at least 1 enabled player to start.
</div>
{% endif %}
</div>
<!-- Current Tournament Status (if active) -->
{% if tournament_state and league_state %}
<div class="section">
<h2 class="section-title">📋 Current Tournament</h2>
<div class="tournament-status">
<div class="tournament-active">🎯 Tournament Active</div>
<div class="tournament-info">
<div class="info-item">
<div class="info-label">Tournament Type</div>
<div class="info-value">
{% if tournament_state.tournament_type == '40_targets' %}
40 Targets
{% else %}
20 Targets
{% endif %}
</div>
</div>
<div class="info-item">
<div class="info-label">Total Players</div>
<div class="info-value">{{ tournament_state.total_players }}</div>
</div>
<div class="info-item">
<div class="info-label">Total Rounds</div>
<div class="info-value">{{ tournament_state.total_rounds }}</div>
</div>
<div class="info-item">
<div class="info-label">Current Round</div>
<div class="info-value">{{ tournament_state.current_round }}</div>
</div>
{% if league_state %}
<div class="info-item">
<div class="info-label">League Tournament</div>
<div class="info-value">{{ tournament_state.league_tournament_number or 'N/A' }}</div>
</div>
{% endif %}
</div>
<div class="action-buttons">
<a href="/tournament/draft" class="action-btn">📋 View Draft</a>
<a href="/results/calculator" class="action-btn success">🎯 Score Tournament</a>
<a href="/" class="action-btn">📺 Dashboard</a>
<button class="action-btn danger" onclick="resetTournament()">🗑️ Reset Tournament</button>
</div>
</div>
</div>
{% endif %}
<!-- Player Management Section -->
{% if not league_state and not tournament_state %}
<div class="section">
<h2 class="section-title">👥 Player Management</h2>
<!-- Add Player Form -->
<div class="add-player-form">
<input type="text"
id="newPlayerName"
placeholder="Enter player name..."
maxlength="50"
onkeypress="handleAddPlayerKeypress(event)">
<button class="add-btn" id="addPlayerBtn" onclick="addPlayer()">
Add Player
</button>
<button class="uncheck-all-btn" onclick="uncheckAllPlayers()">
❌ Uncheck All
</button>
</div>
<!-- Player Grid -->
<div class="player-grid" id="playerGrid">
<!-- Players will be loaded here -->
</div>
</div>
{% endif %}
</div>
<!-- Confirmation Modal -->
<div class="modal-overlay" id="confirmationModal">
<div class="confirmation-modal">
<div class="modal-title" id="modalTitle">Confirm Action</div>
<div class="modal-message" id="modalMessage">Are you sure you want to proceed?</div>
<div class="modal-buttons">
<button class="modal-btn cancel" onclick="closeModal()">Cancel</button>
<button class="modal-btn confirm" id="confirmBtn" onclick="confirmAction()">Confirm</button>
</div>
</div>
</div>
<script>
let players = {{ players|tojson }};
const leagueActive = {{ 'true' if league_state and not league_state.league_finished else 'false' }};
const tournamentActive = {{ 'true' if tournament_state else 'false' }};
let pendingAction = null;
let editingPlayer = null;
let selectedTournamentType = '20_targets';
// Tournament type selection
function selectTournamentType(type) {
selectedTournamentType = type;
// Update UI
document.querySelectorAll('.type-option').forEach(option => {
option.classList.remove('selected');
});
event.currentTarget.classList.add('selected');
// Update radio button
const radio = event.currentTarget.querySelector('input[type="radio"]');
if (radio) radio.checked = true;
}
// Render players grid
function renderPlayerGrid() {
const playerGrid = document.getElementById('playerGrid');
playerGrid.innerHTML = '';
const enabledCount = players.filter(p => p.enabled).length;
document.getElementById('enabledCount').textContent = enabledCount;
// Update button states
const startLeagueBtn = document.getElementById('startLeagueBtn');
const startSingleBtn = document.getElementById('startSingleBtn');
const warningMsg = document.getElementById('warningMessage');
if (startLeagueBtn && startSingleBtn && warningMsg) {
if (enabledCount < 1) {
startLeagueBtn.disabled = true;
startSingleBtn.disabled = true;
warningMsg.style.display = 'block';
} else {
startLeagueBtn.disabled = false;
startSingleBtn.disabled = false;
warningMsg.style.display = 'none';
}
}
players.forEach(player => {
const card = document.createElement('div');
card.className = `player-card ${player.enabled ? 'competing' : 'not-competing'}`;
card.innerHTML = `
<div class="player-header">
<div class="player-id-badge">#${player.id}</div>
<div class="player-status ${player.enabled ? 'status-competing' : 'status-not-competing'}">
${player.enabled ? '✓ Competing' : '✗ Not Competing'}
</div>
</div>
<div class="player-name-section">
<div class="player-name" id="playerName${player.id}">
${player.name}
</div>
</div>
<div class="player-controls">
<div class="competing-toggle" onclick="togglePlayer(${player.id})">
<input type="checkbox" class="toggle-checkbox" ${player.enabled ? 'checked' : ''} onchange="togglePlayer(${player.id})">
<span class="toggle-label">Competing</span>
</div>
<div class="card-buttons">
<button class="edit-btn" onclick="editPlayer(${player.id})" title="Edit Player Name">
✏️ Edit
</button>
<button class="delete-btn" onclick="confirmDeletePlayer(${player.id})" title="Delete Player">
🗑️ Delete
</button>
</div>
</div>
`;
playerGrid.appendChild(card);
});
}
// Add new player
async function addPlayer() {
const nameInput = document.getElementById('newPlayerName');
const name = nameInput.value.trim();
if (!name) {
alert('Please enter a player name');
return;
}
const addBtn = document.getElementById('addPlayerBtn');
addBtn.disabled = true;
addBtn.textContent = 'Adding...';
try {
const response = await fetch('/api/players/add', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: name })
});
if (response.ok) {
const result = await response.json();
players.push(result.player);
nameInput.value = '';
renderPlayerGrid();
} else {
const error = await response.json();
alert('Failed to add player: ' + error.message);
}
} catch (error) {
console.error('Error adding player:', error);
alert('Error adding player. Please try again.');
} finally {
addBtn.disabled = false;
addBtn.textContent = ' Add Player';
}
}
// Handle Enter key in add player input
function handleAddPlayerKeypress(event) {
if (event.key === 'Enter') {
event.preventDefault();
addPlayer();
}
}
// Edit player name
function editPlayer(playerId) {
const player = players.find(p => p.id === playerId);
if (!player) return;
const nameElement = document.getElementById(`playerName${playerId}`);
if (editingPlayer === playerId) {
// Save changes
const input = nameElement.querySelector('input');
if (input) {
const newName = input.value.trim();
if (newName && newName !== player.name) {
updatePlayerName(playerId, newName);
} else {
editingPlayer = null;
renderPlayerGrid();
}
}
} else {
// Start editing
editingPlayer = playerId;
nameElement.innerHTML = `
<input type="text" value="${player.name}" maxlength="50"
onblur="savePlayerName(${playerId})"
onkeypress="handleEditKeypress(event, ${playerId})"
autofocus>
`;
}
}
function savePlayerName(playerId) {
editPlayer(playerId);
}
function handleEditKeypress(event, playerId) {
if (event.key === 'Enter') {
event.preventDefault();
editPlayer(playerId);
} else if (event.key === 'Escape') {
editingPlayer = null;
renderPlayerGrid();
}
}
// Update player name
async function updatePlayerName(playerId, newName) {
try {
const response = await fetch(`/api/players/${playerId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: newName.trim() })
});
if (response.ok) {
// Update local player data
const player = players.find(p => p.id === playerId);
if (player) {
player.name = newName.trim();
}
editingPlayer = null;
renderPlayerGrid();
} else {
alert('Failed to update player name');
editingPlayer = null;
renderPlayerGrid();
}
} catch (error) {
console.error('Error updating player:', error);
editingPlayer = null;
renderPlayerGrid();
}
}
// Toggle player enabled/disabled
async function togglePlayer(playerId) {
const player = players.find(p => p.id === playerId);
if (!player) return;
const newEnabled = !player.enabled;
try {
const response = await fetch(`/api/players/${playerId}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ enabled: newEnabled })
});
if (response.ok) {
player.enabled = newEnabled;
renderPlayerGrid();
} else {
alert('Failed to update player status');
}
} catch (error) {
console.error('Error toggling player:', error);
alert('Error updating player. Please try again.');
}
}
// Uncheck all players
async function uncheckAllPlayers() {
const competingPlayers = players.filter(p => p.enabled);
if (competingPlayers.length === 0) {
alert('No players are currently competing');
return;
}
if (!confirm(`Are you sure you want to uncheck all ${competingPlayers.length} competing players?`)) {
return;
}
try {
// Update each player that is currently enabled
for (const player of competingPlayers) {
const response = await fetch(`/api/players/${player.id}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ enabled: false })
});
if (response.ok) {
player.enabled = false;
}
}
renderPlayerGrid();
} catch (error) {
console.error('Error unchecking players:', error);
alert('Error unchecking players. Please try again.');
}
}
// Confirm delete player
function confirmDeletePlayer(playerId) {
const player = players.find(p => p.id === playerId);
if (!player) return;
showModal(
'Delete Player',
`Are you sure you want to permanently delete "${player.name}"? This action cannot be undone.`,
() => deletePlayer(playerId)
);
}
// Delete player
async function deletePlayer(playerId) {
try {
const response = await fetch(`/api/players/${playerId}/delete`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
});
if (response.ok) {
players = players.filter(p => p.id !== playerId);
renderPlayerGrid();
closeModal();
} else {
alert('Failed to delete player');
}
} catch (error) {
console.error('Error deleting player:', error);
alert('Error deleting player. Please try again.');
}
}
// Start league
async function startLeague() {
const enabledPlayers = players.filter(p => p.enabled);
if (enabledPlayers.length < 1) {
alert('Need at least 1 enabled player to start league');
return;
}
if (!confirm(`Start league with ${enabledPlayers.length} players using ${selectedTournamentType === '40_targets' ? '40' : '20'} targets format?`)) {
return;
}
const startBtn = document.getElementById('startLeagueBtn');
startBtn.disabled = true;
startBtn.textContent = 'Starting League...';
try {
const response = await fetch('/api/league/start', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ tournament_type: selectedTournamentType })
});
if (response.ok) {
const result = await response.json();
alert('League started successfully!');
window.location.reload();
} else {
const error = await response.json();
alert('Failed to start league: ' + error.message);
}
} catch (error) {
console.error('Error starting league:', error);
alert('Error starting league. Please try again.');
} finally {
startBtn.disabled = false;
startBtn.textContent = '🏆 Start League (6 Tournaments)';
}
}
// Start single tournament
async function startSingleTournament() {
const enabledPlayers = players.filter(p => p.enabled);
if (enabledPlayers.length < 1) {
alert('Need at least 1 enabled player to start tournament');
return;
}
if (!confirm(`Start single tournament with ${enabledPlayers.length} players using ${selectedTournamentType === '40_targets' ? '40' : '20'} targets format?`)) {
return;
}
const startBtn = document.getElementById('startSingleBtn');
startBtn.disabled = true;
startBtn.textContent = 'Starting...';
try {
const response = await fetch('/api/tournament/start', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ tournament_type: selectedTournamentType })
});
if (response.ok) {
const result = await response.json();
alert('Tournament started successfully!');
window.location.reload();
} else {
const error = await response.json();
alert('Failed to start tournament: ' + error.message);
}
} catch (error) {
console.error('Error starting tournament:', error);
alert('Error starting tournament. Please try again.');
} finally {
startBtn.disabled = false;
startBtn.textContent = '🎯 Start Single Tournament';
}
}
// Start next tournament in league
async function startNextTournament() {
// Get selected joker players
const jokerCheckboxes = document.querySelectorAll('.joker-checkbox:checked:not(:disabled)');
const jokerPlayers = Array.from(jokerCheckboxes).map(cb => parseInt(cb.dataset.playerId));
if (!confirm(`Start next tournament? ${jokerPlayers.length} players will use their joker.`)) {
return;
}
try {
const response = await fetch('/api/league/tournament/start', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ joker_players: jokerPlayers })
});
if (response.ok) {
const result = await response.json();
alert('Tournament started successfully!');
window.location.reload();
} else {
const error = await response.json();
alert('Failed to start tournament: ' + error.message);
}
} catch (error) {
console.error('Error starting tournament:', error);
alert('Error starting tournament. Please try again.');
}
}
// Reset league
async function resetLeague() {
if (!confirm('Are you sure you want to reset the league? This will delete all league and tournament data.')) {
return;
}
try {
const response = await fetch('/api/league/reset', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
});
if (response.ok) {
alert('League reset successfully!');
window.location.reload();
} else {
alert('Failed to reset league');
}
} catch (error) {
console.error('Error resetting league:', error);
alert('Error resetting league. Please try again.');
}
}
// Reset tournament
async function resetTournament() {
if (!confirm('Are you sure you want to reset the tournament? This will delete tournament data.')) {
return;
}
try {
const response = await fetch('/api/tournament/reset', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
});
if (response.ok) {
alert('Tournament reset successfully!');
window.location.reload();
} else {
alert('Failed to reset tournament');
}
} catch (error) {
console.error('Error resetting tournament:', error);
alert('Error resetting tournament. Please try again.');
}
}
// Modal functions
function showModal(title, message, confirmCallback) {
document.getElementById('modalTitle').textContent = title;
document.getElementById('modalMessage').textContent = message;
pendingAction = confirmCallback;
document.getElementById('confirmationModal').classList.add('active');
}
function closeModal() {
document.getElementById('confirmationModal').classList.remove('active');
pendingAction = null;
}
function confirmAction() {
if (pendingAction) {
pendingAction();
}
}
// Click outside modal to close
document.getElementById('confirmationModal').addEventListener('click', function(e) {
if (e.target === this) {
closeModal();
}
});
// Initialize page
document.addEventListener('DOMContentLoaded', function() {
renderPlayerGrid();
// Focus on add player input if no active league/tournament
const nameInput = document.getElementById('newPlayerName');
if (nameInput && !leagueActive && !tournamentActive) {
nameInput.focus();
}
console.log('🏆 Tournament Management loaded');
console.log('League active:', leagueActive);
console.log('Tournament active:', tournamentActive);
});
</script>
</body>
</html>