1198 lines
46 KiB
HTML
1198 lines
46 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8" />
|
|
<title data-i18n="tournament.tournament_management">Tournament Management</title>
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<link rel="stylesheet" href="/static/css/base.css">
|
|
<link rel="stylesheet" href="/static/css/navbar.css">
|
|
<link rel="stylesheet" href="/static/css/buttons.css">
|
|
<link rel="stylesheet" href="/static/css/components.css">
|
|
<link rel="stylesheet" href="/static/css/responsive.css">
|
|
<link rel="stylesheet" href="/static/css/pages/tournament.css">
|
|
<script src="/static/js/i18n.js"></script>
|
|
</head>
|
|
<body>
|
|
<div class="navbar">
|
|
<div class="navbar-title">🏆 <span data-i18n="tournament.tournament_management">Tournament Management</span></div>
|
|
<div class="navbar-controls">
|
|
<a href="/tournament" class="nav-btn active">🏆 <span data-i18n="navigation.tournament">Tournament</span></a>
|
|
{% if tournament_state %}
|
|
<a href="/tournament/draft" class="nav-btn">📋 Razpored</a>
|
|
<a href="/results/calculator" class="nav-btn">🎯 <span data-i18n="navigation.calculator">Results Calculator</span></a>
|
|
{% endif %}
|
|
<a href="/" class="nav-btn">✕</a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="container">
|
|
<!-- League/Tournament Management Section -->
|
|
<div class="section">
|
|
{% if league_state and not league_state.league_finished %}
|
|
<h2 class="section-title" data-i18n="league.league_management">🏆 Upravljanje Lige</h2>
|
|
|
|
<!-- League Info Card -->
|
|
<div class="league-tournament-container {% if not tournament_state and league_state.current_tournament >= league_state.total_tournaments %}single-card no-stretch{% endif %}">
|
|
<!-- League Card -->
|
|
<div class="league-tournament-card league">
|
|
<h3>🏆 <span data-i18n="league.league_active">Liga (Aktivna)</span></h3>
|
|
<div class="unified-status league">
|
|
<span data-i18n="league.league_active">Liga je Aktivna</span>
|
|
</div>
|
|
<div class="compact-info-grid">
|
|
<div class="compact-info-item">
|
|
<div class="compact-info-label" data-i18n="tournament.tournament_type">Tip</div>
|
|
<div class="compact-info-value">
|
|
{% if league_state.tournament_type == '40_targets' %}
|
|
💪 40
|
|
{% elif league_state.tournament_type == '4_targets' %}
|
|
🎯 4
|
|
{% else %}
|
|
⚡ 20
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="compact-info-item">
|
|
<div class="compact-info-label" data-i18n="tournament.participants">Igralci</div>
|
|
<div class="compact-info-value">{{ league_state.participants|length }}</div>
|
|
</div>
|
|
<div class="compact-info-item">
|
|
<div class="compact-info-label" data-i18n="tournament.created">Začeto</div>
|
|
<div class="compact-info-value">{{ league_state.created_at[:10] }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tournament Progress Timeline -->
|
|
<div class="rounds-progress-bar">
|
|
<div class="rounds-progress-title">
|
|
📊 <span data-i18n="league.tournaments">Turnirji</span>
|
|
</div>
|
|
<div class="rounds-progress-horizontal">
|
|
{% for i in range(1, league_state.total_tournaments + 1) %}
|
|
{% set is_completed = i <= league_state.completed_tournaments|length %}
|
|
{% set is_active = (i == league_state.current_tournament and tournament_state) or (i == league_state.current_tournament and not tournament_state and not is_completed) %}
|
|
<div class="round-progress-dot {% if is_completed %}completed{% elif is_active %}active{% else %}pending{% endif %}" title="Tournament {{ i }}">
|
|
{{ i }}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
{% if not tournament_state and league_state.current_tournament < league_state.total_tournaments %}
|
|
<div class="action-buttons" style="margin-top: auto; padding-top: 16px;">
|
|
{% if league_state.completed_tournaments|length > 0 %}
|
|
<a href="/results" class="action-btn info">
|
|
📊 <span data-i18n="tournament.view_current_results">View Current Standings</span>
|
|
</a>
|
|
{% endif %}
|
|
<button class="action-btn success" onclick="startNextTournament()">
|
|
🚀 <span data-i18n="league.start_tournament_number">Začni Turnir</span> {{ league_state.current_tournament + 1 }}
|
|
</button>
|
|
<button class="action-btn danger" onclick="stopAndDeleteLeague()">
|
|
🛑 <span data-i18n="league.stop_league">Ustavi Ligo</span>
|
|
</button>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Joker Card (shown when no tournament is active) -->
|
|
{% if not tournament_state and league_state.current_tournament < league_state.total_tournaments %}
|
|
<div class="league-tournament-card joker" id="jokerSection">
|
|
<div class="joker-title">🃏 <span data-i18n="league.joker_selection_for_tournament">Izbira Jokerja za Turnir</span> {{ league_state.current_tournament + 1 }}</div>
|
|
<p style="margin-bottom: 12px; color: #856404; font-size: 0.85rem;" data-i18n="league.joker_instructions">Izberite igralce, ki bodo uporabili svojega Jokerja (preskočili ta turnir). Vsak igralec lahko uporabi svojega Žolna samo enkrat na ligo.</p>
|
|
|
|
<div class="joker-players-card">
|
|
<div class="joker-list">
|
|
{% 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 checked{% endif %}
|
|
data-player-id="{{ player_id }}">
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<!-- Tournament Card (only shown when tournament is active) -->
|
|
{% if tournament_state %}
|
|
<div class="league-tournament-card tournament">
|
|
<h3>🎯 <span data-i18n="tournament.active_tournament">Trenutni Turnir</span></h3>
|
|
<div class="unified-status tournament">
|
|
<span data-i18n="tournament.active_tournament">Turnir je Aktiven</span>
|
|
</div>
|
|
<div class="compact-info-grid">
|
|
<div class="compact-info-item">
|
|
<div class="compact-info-label" data-i18n="players.total_players">Igralci</div>
|
|
<div class="compact-info-value">{{ tournament_state.total_players }}</div>
|
|
</div>
|
|
<div class="compact-info-item">
|
|
<div class="compact-info-label" data-i18n="tournament.total_rounds">Skupaj Krogov</div>
|
|
<div class="compact-info-value">{{ tournament_state.total_rounds }}</div>
|
|
</div>
|
|
<div class="compact-info-item">
|
|
<div class="compact-info-label" data-i18n="tournament.current_round">Trenutni Krog</div>
|
|
<div class="compact-info-value">{{ tournament_state.current_round }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Round Progress Timeline -->
|
|
<div class="rounds-progress-bar">
|
|
<div class="rounds-progress-title">
|
|
📊 <span data-i18n="tournament.round">Krogovi</span> - {{ tournament_state.current_round }}/{{ tournament_state.total_rounds }}
|
|
</div>
|
|
<div class="rounds-progress-horizontal">
|
|
{% for i in range(1, tournament_state.total_rounds + 1) %}
|
|
{% set is_completed = i < tournament_state.current_round %}
|
|
{% set is_active = i == tournament_state.current_round %}
|
|
<div class="round-progress-dot {% if is_completed %}completed{% elif is_active %}active{% else %}pending{% endif %}" title="Round {{ i }}">
|
|
{{ i }}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
|
|
<div class="action-buttons" style="margin-top: auto; padding-top: 12px;">
|
|
<button class="action-btn danger" onclick="cancelLeagueTournament()">🗑️ Ponastavi tekmo</button>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
|
|
|
|
{% if not tournament_state and league_state.current_tournament >= league_state.total_tournaments %}
|
|
<div class="warning">
|
|
<strong data-i18n="league.league_complete">League Complete!</strong> <span data-i18n="league.league_complete_info">All tournaments planned. Finish current for final results.</span>
|
|
</div>
|
|
{% endif %}
|
|
|
|
{% 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" data-i18n="tournament.tournament_type">Tip Turnirja</div>
|
|
<div class="info-value">
|
|
{% if league_state.tournament_type == '40_targets' %}
|
|
40 Targets
|
|
{% elif league_state.tournament_type == '4_targets' %}
|
|
4 Targets
|
|
{% else %}
|
|
20 Targets
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label" data-i18n="tournament.participants">Udeleženci</div>
|
|
<div class="info-value">{{ league_state.participants|length }}</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label" data-i18n="tournament.tournaments">Turnirji</div>
|
|
<div class="info-value">{{ league_state.total_tournaments }}</div>
|
|
</div>
|
|
<div class="info-item">
|
|
<div class="info-label" data-i18n="tournament.finished">Zaključeno</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">🏆 <span data-i18n="league.view_league_results">View League Results</span></a>
|
|
<button class="action-btn danger" onclick="resetLeague()">🗑️ <span data-i18n="league.reset_league">Reset League</span></button>
|
|
</div>
|
|
</div>
|
|
|
|
{% elif not league_state and tournament_state %}
|
|
<h2 class="section-title" data-i18n="tournament.tournament_management">🎯 Upravljanje Turnirja</h2>
|
|
<div class="league-tournament-container single-card">
|
|
<!-- Tournament Card -->
|
|
<div class="league-tournament-card tournament">
|
|
<h3>🎯 <span data-i18n="tournament.active_tournament">Trenutni Turnir</span></h3>
|
|
<div class="unified-status tournament">
|
|
<span data-i18n="tournament.active_tournament">Turnir je Aktiven</span>
|
|
</div>
|
|
<div class="compact-info-grid">
|
|
<div class="compact-info-item">
|
|
<div class="compact-info-label" data-i18n="tournament.tournament_type">Tip</div>
|
|
<div class="compact-info-value">
|
|
{% if tournament_state.tournament_type == '40_targets' %}
|
|
💪 40
|
|
{% elif tournament_state.tournament_type == '4_targets' %}
|
|
🎯 4
|
|
{% else %}
|
|
⚡ 20
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
<div class="compact-info-item">
|
|
<div class="compact-info-label" data-i18n="players.total_players">Igralci</div>
|
|
<div class="compact-info-value">{{ tournament_state.total_players }}</div>
|
|
</div>
|
|
<div class="compact-info-item">
|
|
<div class="compact-info-label" data-i18n="tournament.total_rounds">Skupaj Krogov</div>
|
|
<div class="compact-info-value">{{ tournament_state.total_rounds }}</div>
|
|
</div>
|
|
<div class="compact-info-item">
|
|
<div class="compact-info-label" data-i18n="tournament.current_round">Trenutni Krog</div>
|
|
<div class="compact-info-value">{{ tournament_state.current_round }}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Round Progress Timeline -->
|
|
<div class="rounds-progress-bar">
|
|
<div class="rounds-progress-title">
|
|
📊 <span data-i18n="tournament.round">Kroogi</span> - {{ tournament_state.current_round }}/{{ tournament_state.total_rounds }}
|
|
</div>
|
|
<div class="rounds-progress-horizontal">
|
|
{% for i in range(1, tournament_state.total_rounds + 1) %}
|
|
{% set is_completed = i < tournament_state.current_round %}
|
|
{% set is_active = i == tournament_state.current_round %}
|
|
<div class="round-progress-dot {% if is_completed %}completed{% elif is_active %}active{% else %}pending{% endif %}" title="Round {{ i }}">
|
|
{{ i }}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="action-buttons" style="margin-top: 20px;">
|
|
<button class="action-btn danger" onclick="resetTournament()">🗑️ <span data-i18n="tournament.reset_tournament">Reset Tournament</span></button>
|
|
</div>
|
|
|
|
{% else %}
|
|
<!-- Two-column layout for default state -->
|
|
<div class="default-layout">
|
|
<!-- LEFT: Tournament Setup -->
|
|
<div class="default-left">
|
|
<h2 class="section-title" data-i18n="league.setup">🏁 Nastavitev</h2>
|
|
<!-- Tournament Type Selection -->
|
|
<div class="tournament-type-selection">
|
|
<div class="type-options type-options-vertical">
|
|
<div class="type-option" data-type="4_targets" onclick="selectTournamentType('4_targets')">
|
|
<input type="radio" name="tournament_type" value="4_targets">
|
|
<div class="type-option-text">
|
|
<div class="type-name">🎯 4 Targets</div>
|
|
<div class="type-description" data-i18n="tournament_types.4_targets_desc">Hitri format s 4 tarčami, 5 strelov na tarčo (20 strelov skupaj)</div>
|
|
</div>
|
|
</div>
|
|
<div class="type-option selected" data-type="20_targets" onclick="selectTournamentType('20_targets')">
|
|
<input type="radio" name="tournament_type" value="20_targets" checked>
|
|
<div class="type-option-text">
|
|
<div class="type-name">⚡ 20 Targets</div>
|
|
<div class="type-description" data-i18n="tournament_types.20_targets_desc">Standardni format z 20 tarčami, 2 strela na tarčo (40 strelov skupaj)</div>
|
|
</div>
|
|
</div>
|
|
<div class="type-option" data-type="40_targets" onclick="selectTournamentType('40_targets')">
|
|
<input type="radio" name="tournament_type" value="40_targets">
|
|
<div class="type-option-text">
|
|
<div class="type-name">💪 40 Targets</div>
|
|
<div class="type-description" data-i18n="tournament_types.40_targets_desc">Razširjeni format s 40 tarčami, 2 strela na tarčo (80 strelov skupaj)</div>
|
|
</div>
|
|
</div>
|
|
<div class="type-option" data-type="nabijanje" onclick="selectTournamentType('nabijanje')">
|
|
<input type="radio" name="tournament_type" value="nabijanje">
|
|
<div class="type-option-text">
|
|
<div class="type-name">💥 Nabijanje</div>
|
|
<div class="type-description">Prosti format z nastavljivo konfiguracijo tarč in strelov</div>
|
|
</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()">🏆 <span data-i18n="league.start_league_5_tournaments">Začni Ligo (5 Turnirjev)</span></button>
|
|
<button class="action-btn" id="startSingleBtn" onclick="startSingleTournament()">🏅 <span data-i18n="league.start_single_tournament">Začni Posamezen Turnir</span></button>
|
|
</div>
|
|
|
|
<div class="warning" id="warningMessage" style="display: none;">
|
|
<strong>Note:</strong> You need at least 1 enabled player to start.
|
|
</div>
|
|
</div>
|
|
|
|
<!-- RIGHT: Player Management -->
|
|
<div class="default-right">
|
|
<h2 class="section-title" data-i18n="players.player_management">👥 Upravljanje Igralcev</h2>
|
|
|
|
<!-- Add Player -->
|
|
<div class="add-player-row">
|
|
<input type="text"
|
|
id="newPlayerName"
|
|
data-i18n="[placeholder]league.enter_player_name"
|
|
placeholder="Vnesite ime igralca..."
|
|
maxlength="50"
|
|
onkeypress="handleAddPlayerKeypress(event)">
|
|
<button class="add-btn" id="addPlayerBtn" onclick="addPlayer()">
|
|
<span data-i18n="players.add_player">Dodaj</span>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Toolbar: search + filters -->
|
|
<div class="player-toolbar">
|
|
<div class="search-container">
|
|
<input type="text"
|
|
class="search-input"
|
|
id="playerSearch"
|
|
data-i18n="[placeholder]league.search_players_placeholder"
|
|
placeholder="Išči..."
|
|
oninput="filterPlayers()">
|
|
<span class="search-icon">🔍</span>
|
|
</div>
|
|
<button class="filter-btn active" data-filter="all" onclick="setFilter('all')" data-i18n="general.all">Vse</button>
|
|
<button class="filter-btn" data-filter="enabled" onclick="setFilter('enabled')" data-i18n="players.enabled">✓</button>
|
|
<button class="filter-btn" data-filter="disabled" onclick="setFilter('disabled')" data-i18n="players.disabled">✗</button>
|
|
</div>
|
|
|
|
<!-- Bulk actions + stats -->
|
|
<div class="bulk-strip">
|
|
<input type="checkbox" id="selectAllCheckbox" onchange="toggleSelectAll()" title="Select all">
|
|
<button class="bulk-btn" onclick="enableSelected()" data-i18n="general.enable_selected">Omogoči</button>
|
|
<button class="bulk-btn danger" onclick="disableSelected()" data-i18n="general.disable_selected">Onemogoči</button>
|
|
<div class="stats-bar" style="margin:0; flex:1; justify-content:flex-end;">
|
|
<span><strong class="stat-enabled" id="enabledPlayersCount">0</strong> ✓</span>
|
|
<span><strong class="stat-disabled" id="disabledPlayersCount">0</strong> ✗</span>
|
|
<span><strong id="totalPlayersCount">0</strong> skupaj</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Player pill list -->
|
|
<div class="player-list" id="playerList">
|
|
<!-- Pills rendered by JS -->
|
|
</div>
|
|
|
|
<div class="no-results" id="noResults" style="display: none;">
|
|
<span data-i18n="league.no_players_found">Ni najdenih igralcev.</span>
|
|
</div>
|
|
</div><!-- end default-right -->
|
|
</div><!-- end default-layout -->
|
|
{% endif %}
|
|
</div><!-- end section -->
|
|
</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';
|
|
let currentFilter = 'all';
|
|
let searchTerm = '';
|
|
let selectedPlayers = new Set();
|
|
|
|
// Tournament type selection
|
|
function selectTournamentType(type) {
|
|
selectedTournamentType = type;
|
|
|
|
document.querySelectorAll('.type-option').forEach(option => {
|
|
option.classList.remove('selected');
|
|
});
|
|
event.currentTarget.classList.add('selected');
|
|
const radio = event.currentTarget.querySelector('input[type="radio"]');
|
|
if (radio) radio.checked = true;
|
|
|
|
const isNabijanje = type === 'nabijanje';
|
|
document.getElementById('startLeagueBtn').style.display = isNabijanje ? 'none' : '';
|
|
document.getElementById('startSingleBtn').textContent = isNabijanje
|
|
? '💥 Odpri Nabijanje'
|
|
: '🏅 ' + (window._i18n?.['league.start_single_tournament'] || 'Začni Posamezen Turnir');
|
|
}
|
|
|
|
// Filter and search functions
|
|
function setFilter(filter) {
|
|
currentFilter = filter;
|
|
|
|
// Update filter buttons
|
|
document.querySelectorAll('.filter-btn').forEach(btn => {
|
|
btn.classList.remove('active');
|
|
});
|
|
document.querySelector(`[data-filter="${filter}"]`).classList.add('active');
|
|
|
|
renderPlayerTable();
|
|
}
|
|
|
|
function filterPlayers() {
|
|
searchTerm = document.getElementById('playerSearch').value.toLowerCase();
|
|
renderPlayerTable();
|
|
}
|
|
|
|
function getFilteredPlayers() {
|
|
let filtered = players.filter(player => {
|
|
// Search filter
|
|
if (searchTerm && !player.name.toLowerCase().includes(searchTerm)) {
|
|
return false;
|
|
}
|
|
|
|
// Status filter
|
|
if (currentFilter === 'enabled' && !player.enabled) {
|
|
return false;
|
|
}
|
|
if (currentFilter === 'disabled' && player.enabled) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
});
|
|
|
|
// Sort by enabled status first, then by name
|
|
filtered.sort((a, b) => {
|
|
if (a.enabled && !b.enabled) return -1;
|
|
if (!a.enabled && b.enabled) return 1;
|
|
return a.name.localeCompare(b.name);
|
|
});
|
|
|
|
return filtered;
|
|
}
|
|
|
|
// Selection functions
|
|
function togglePlayerSelection(playerId, checked) {
|
|
if (checked) {
|
|
selectedPlayers.add(playerId);
|
|
} else {
|
|
selectedPlayers.delete(playerId);
|
|
}
|
|
updateSelectAllCheckbox();
|
|
}
|
|
|
|
function toggleSelectAll() {
|
|
const selectAllCheckbox = document.getElementById('selectAllCheckbox');
|
|
const visiblePlayers = getFilteredPlayers();
|
|
|
|
if (selectAllCheckbox.checked) {
|
|
visiblePlayers.forEach(player => selectedPlayers.add(player.id));
|
|
} else {
|
|
visiblePlayers.forEach(player => selectedPlayers.delete(player.id));
|
|
}
|
|
|
|
renderPlayerTable();
|
|
}
|
|
|
|
function selectAllVisible() {
|
|
const visiblePlayers = getFilteredPlayers();
|
|
visiblePlayers.forEach(player => selectedPlayers.add(player.id));
|
|
renderPlayerTable();
|
|
}
|
|
|
|
function updateSelectAllCheckbox() {
|
|
const selectAllCheckbox = document.getElementById('selectAllCheckbox');
|
|
const visiblePlayers = getFilteredPlayers();
|
|
const selectedVisible = visiblePlayers.filter(p => selectedPlayers.has(p.id));
|
|
|
|
if (selectedVisible.length === 0) {
|
|
selectAllCheckbox.checked = false;
|
|
selectAllCheckbox.indeterminate = false;
|
|
} else if (selectedVisible.length === visiblePlayers.length) {
|
|
selectAllCheckbox.checked = true;
|
|
selectAllCheckbox.indeterminate = false;
|
|
} else {
|
|
selectAllCheckbox.checked = false;
|
|
selectAllCheckbox.indeterminate = true;
|
|
}
|
|
}
|
|
|
|
// Bulk actions
|
|
function enableSelected() {
|
|
if (selectedPlayers.size === 0) {
|
|
alert(t('league.no_players_selected'));
|
|
return;
|
|
}
|
|
|
|
if (!confirm(`Enable ${selectedPlayers.size} selected players?`)) {
|
|
return;
|
|
}
|
|
|
|
bulkUpdatePlayers(Array.from(selectedPlayers), true);
|
|
}
|
|
|
|
function disableSelected() {
|
|
if (selectedPlayers.size === 0) {
|
|
alert(t('league.no_players_selected'));
|
|
return;
|
|
}
|
|
|
|
if (!confirm(`Disable ${selectedPlayers.size} selected players?`)) {
|
|
return;
|
|
}
|
|
|
|
bulkUpdatePlayers(Array.from(selectedPlayers), false);
|
|
}
|
|
|
|
async function bulkUpdatePlayers(playerIds, enabled) {
|
|
try {
|
|
for (const playerId of playerIds) {
|
|
const response = await fetch(`/api/players/${playerId}`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
},
|
|
body: JSON.stringify({ enabled: enabled })
|
|
});
|
|
|
|
if (response.ok) {
|
|
const player = players.find(p => p.id === playerId);
|
|
if (player) {
|
|
player.enabled = enabled;
|
|
}
|
|
}
|
|
}
|
|
|
|
selectedPlayers.clear();
|
|
renderPlayerTable();
|
|
updateStats();
|
|
} catch (error) {
|
|
console.error('Error bulk updating players:', error);
|
|
alert('Error updating players. Please try again.');
|
|
}
|
|
}
|
|
|
|
// Update stats display
|
|
function updateStats() {
|
|
const totalCount = players.length;
|
|
const enabledCount = players.filter(p => p.enabled).length;
|
|
const disabledCount = totalCount - enabledCount;
|
|
const visibleCount = getFilteredPlayers().length;
|
|
|
|
const setEl = (id, val) => { const el = document.getElementById(id); if (el) el.textContent = val; };
|
|
setEl('totalPlayersCount', totalCount);
|
|
setEl('enabledPlayersCount', enabledCount);
|
|
setEl('disabledPlayersCount', disabledCount);
|
|
setEl('visiblePlayersCount', visibleCount);
|
|
setEl('enabledCount', 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';
|
|
}
|
|
}
|
|
}
|
|
|
|
// Render the player list as pills
|
|
function renderPlayerTable() {
|
|
const list = document.getElementById('playerList');
|
|
const noResults = document.getElementById('noResults');
|
|
|
|
// Check if playerList exists before proceeding (it may not exist on all pages)
|
|
if (!list) {
|
|
return;
|
|
}
|
|
|
|
const filteredPlayers = getFilteredPlayers();
|
|
|
|
list.innerHTML = '';
|
|
|
|
if (filteredPlayers.length === 0) {
|
|
if (noResults) noResults.style.display = 'block';
|
|
updateSelectAllCheckbox();
|
|
updateStats();
|
|
return;
|
|
}
|
|
|
|
if (noResults) noResults.style.display = 'none';
|
|
|
|
filteredPlayers.forEach(player => {
|
|
const row = document.createElement('div');
|
|
row.className = `player-row${player.enabled ? '' : ' disabled-player'}${selectedPlayers.has(player.id) ? ' selected' : ''}`;
|
|
row.setAttribute('data-player-id', player.id);
|
|
|
|
if (editingPlayer === player.id) {
|
|
row.innerHTML = `
|
|
<span class="pr-dot"></span>
|
|
<span class="pr-name" id="playerName${player.id}">
|
|
<input type="text" value="${player.name}" maxlength="50"
|
|
onblur="savePlayerName(${player.id})"
|
|
onkeydown="handleEditKeypress(event, ${player.id})"
|
|
autofocus>
|
|
</span>
|
|
<div class="pr-actions" style="opacity:1;">
|
|
<button class="pr-btn" onclick="editPlayer(${player.id})" title="Shrani">💾</button>
|
|
</div>
|
|
`;
|
|
} else {
|
|
row.innerHTML = `
|
|
<input type="checkbox" class="pr-check"
|
|
${selectedPlayers.has(player.id) ? 'checked' : ''}
|
|
onclick="event.stopPropagation()"
|
|
onchange="togglePlayerSelection(${player.id}, this.checked)">
|
|
<span class="pr-dot" onclick="event.stopPropagation(); togglePlayer(${player.id})" title="${player.enabled ? 'Onemogoči' : 'Omogoči'}"></span>
|
|
<span class="pr-name" id="playerName${player.id}">${player.name}</span>
|
|
<div class="pr-actions">
|
|
<button class="pr-btn" onclick="event.stopPropagation(); editPlayer(${player.id})" title="Uredi">✏️</button>
|
|
<button class="pr-btn del" onclick="event.stopPropagation(); confirmDeletePlayer(${player.id})" title="Izbriši">🗑</button>
|
|
</div>
|
|
`;
|
|
// Row click toggles selection
|
|
row.addEventListener('click', () => {
|
|
const cb = row.querySelector('.pr-check');
|
|
cb.checked = !cb.checked;
|
|
togglePlayerSelection(player.id, cb.checked);
|
|
});
|
|
}
|
|
|
|
list.appendChild(row);
|
|
});
|
|
|
|
updateSelectAllCheckbox();
|
|
updateStats();
|
|
}
|
|
|
|
// 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 = '';
|
|
renderPlayerTable();
|
|
} 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.innerHTML = '<span data-i18n="players.add_player">Dodaj Igralca</span>';
|
|
translatePage();
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
|
|
if (editingPlayer === playerId) {
|
|
// Save changes
|
|
const nameElement = document.getElementById(`playerName${playerId}`);
|
|
const input = nameElement ? nameElement.querySelector('input') : null;
|
|
if (input) {
|
|
const newName = input.value.trim();
|
|
if (newName && newName !== player.name) {
|
|
updatePlayerName(playerId, newName);
|
|
} else {
|
|
editingPlayer = null;
|
|
renderPlayerTable();
|
|
}
|
|
}
|
|
} else {
|
|
// Start editing — re-render the whole list so the pill shows input
|
|
editingPlayer = playerId;
|
|
renderPlayerTable();
|
|
// Focus the input after render
|
|
setTimeout(() => {
|
|
const nameElement = document.getElementById(`playerName${playerId}`);
|
|
const input = nameElement ? nameElement.querySelector('input') : null;
|
|
if (input) input.focus();
|
|
}, 20);
|
|
}
|
|
}
|
|
|
|
function savePlayerName(playerId) {
|
|
editPlayer(playerId);
|
|
}
|
|
|
|
function handleEditKeypress(event, playerId) {
|
|
if (event.key === 'Enter') {
|
|
event.preventDefault();
|
|
editPlayer(playerId);
|
|
} else if (event.key === 'Escape') {
|
|
editingPlayer = null;
|
|
renderPlayerTable();
|
|
}
|
|
}
|
|
|
|
// 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;
|
|
renderPlayerTable();
|
|
} else {
|
|
alert('Failed to update player name');
|
|
editingPlayer = null;
|
|
renderPlayerTable();
|
|
}
|
|
} catch (error) {
|
|
console.error('Error updating player:', error);
|
|
editingPlayer = null;
|
|
renderPlayerTable();
|
|
}
|
|
}
|
|
|
|
// Update a single player row without re-rendering the entire list
|
|
function updatePlayerRow(playerId) {
|
|
const player = players.find(p => p.id === playerId);
|
|
if (!player) return;
|
|
|
|
const row = document.querySelector(`.player-row[data-player-id="${playerId}"]`);
|
|
if (!row) return;
|
|
|
|
if (player.enabled) {
|
|
row.classList.remove('disabled-player');
|
|
} else {
|
|
row.classList.add('disabled-player');
|
|
}
|
|
|
|
updateStats();
|
|
}
|
|
|
|
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;
|
|
updatePlayerRow(playerId);
|
|
} else {
|
|
alert('Failed to update player status');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error toggling player:', error);
|
|
alert('Error updating player. 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);
|
|
selectedPlayers.delete(playerId);
|
|
renderPlayerTable();
|
|
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;
|
|
}
|
|
|
|
const formatText = selectedTournamentType === '40_targets' ? t('tournament_types.40_targets_full') :
|
|
selectedTournamentType === '4_targets' ? t('tournament_types.4_targets_full') :
|
|
t('tournament_types.20_targets_full');
|
|
|
|
if (!confirm(t('messages.confirm_start_league', { players: enabledPlayers.length, format: formatText }))) {
|
|
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.setAttribute('data-i18n', 'league.start_league_5_tournaments');
|
|
startBtn.textContent = '🎖️ Začni Ligo (5 Turnirjev)';
|
|
translatePage();
|
|
}
|
|
}
|
|
|
|
// Start single tournament
|
|
async function startSingleTournament() {
|
|
if (selectedTournamentType === 'nabijanje') {
|
|
window.location.href = '/calculator';
|
|
return;
|
|
}
|
|
|
|
const enabledPlayers = players.filter(p => p.enabled);
|
|
|
|
if (enabledPlayers.length < 1) {
|
|
alert('Need at least 1 enabled player to start tournament');
|
|
return;
|
|
}
|
|
|
|
const formatText = selectedTournamentType === '40_targets' ? t('tournament_types.40_targets_full') :
|
|
selectedTournamentType === '4_targets' ? t('tournament_types.4_targets_full') :
|
|
t('tournament_types.20_targets_full');
|
|
|
|
if (!confirm(t('messages.confirm_start_tournament', { players: enabledPlayers.length, format: formatText }))) {
|
|
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));
|
|
|
|
const confirmMessage = jokerPlayers.length === 1 ?
|
|
t('league.start_tournament_confirm_single') :
|
|
t('league.start_tournament_confirm_multiple').replace('{count}', jokerPlayers.length);
|
|
if (!confirm(confirmMessage)) {
|
|
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.');
|
|
}
|
|
}
|
|
|
|
// Stop league (pause current tournament and keep league data)
|
|
async function stopLeague() {
|
|
if (!confirm('Are you sure you want to stop the league? The current tournament will be stopped but league data will be kept.')) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch('/api/tournament/reset', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
}
|
|
});
|
|
|
|
if (response.ok) {
|
|
alert('Tournament stopped successfully! League is paused.');
|
|
window.location.reload();
|
|
} else {
|
|
alert('Failed to stop tournament');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error stopping tournament:', error);
|
|
alert('Error stopping tournament. Please try again.');
|
|
}
|
|
}
|
|
|
|
// Stop and delete league
|
|
async function stopAndDeleteLeague() {
|
|
if (!confirm('Are you sure you want to stop and delete this league? This action cannot be undone. All league data will be deleted.')) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch('/api/league/reset', {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
}
|
|
});
|
|
|
|
if (response.ok) {
|
|
alert('League has been stopped and deleted successfully!');
|
|
window.location.reload();
|
|
} else {
|
|
const error = await response.json();
|
|
alert('Failed to delete league: ' + (error.message || 'Unknown error'));
|
|
}
|
|
} catch (error) {
|
|
console.error('Error deleting league:', error);
|
|
alert('Error deleting 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.');
|
|
}
|
|
}
|
|
|
|
// Cancel tournament within a league (rolls back tournament counter)
|
|
async function cancelLeagueTournament() {
|
|
if (!confirm('Ponastavi tekmo? Liga ostane na istem turnirju.')) {
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const response = await fetch('/api/tournament/cancel', {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' }
|
|
});
|
|
|
|
if (response.ok) {
|
|
window.location.reload();
|
|
} else {
|
|
alert('Napaka pri ponastavitvi tekme.');
|
|
}
|
|
} catch (error) {
|
|
console.error('Error cancelling tournament:', error);
|
|
alert('Napaka. Poskusite znova.');
|
|
}
|
|
}
|
|
|
|
// 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() {
|
|
// Wait for translations to be ready before rendering the player table
|
|
window.addEventListener('i18nReady', function() {
|
|
renderPlayerTable();
|
|
|
|
// 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);
|
|
|
|
{% if league_state %}
|
|
console.log('📊 League State Debug:');
|
|
console.log(' - Completed tournaments:', {{ league_state.completed_tournaments|length }});
|
|
console.log(' - Total tournaments:', {{ league_state.total_tournaments }});
|
|
console.log(' - Current tournament:', {{ league_state.current_tournament }});
|
|
console.log(' - Completed list:', {{ league_state.completed_tournaments|tojson }});
|
|
{% endif %}
|
|
}, { once: true });
|
|
});
|
|
|
|
</script>
|
|
</body>
|
|
</html> |