Files
Sdk_TV_app/templates/tournament.html
T

2675 lines
75 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 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">
<style>
/* Tournament-specific styles */
/* Specific tournament styles */
.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;
}
/* Unified League & Tournament Info Layout */
.league-tournament-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 25px;
margin: 20px 0;
}
.league-tournament-card {
background: white;
border: 2px solid #dee2e6;
border-radius: 12px;
padding: 20px;
}
.league-tournament-card h3 {
font-size: 1.1rem;
font-weight: bold;
color: #333;
margin: 0 0 15px 0;
padding-bottom: 10px;
border-bottom: 3px solid #007bff;
}
.league-tournament-card.league h3 {
border-bottom-color: #28a745;
}
.league-tournament-card.tournament h3 {
border-bottom-color: #007bff;
}
.compact-info-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px;
}
.compact-info-item {
background: #f8f9fa;
padding: 12px;
border-radius: 6px;
border: 1px solid #e9ecef;
}
.compact-info-label {
font-size: 0.85rem;
color: #666;
margin-bottom: 4px;
}
.compact-info-value {
font-size: 1.2rem;
font-weight: bold;
color: #333;
}
.unified-status {
margin-bottom: 15px;
padding: 10px;
border-radius: 6px;
text-align: center;
font-weight: bold;
font-size: 0.95rem;
}
.unified-status.league {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.unified-status.tournament {
background: #cfe2ff;
color: #084298;
border: 1px solid #b6d4fe;
}
/* League Progress Bar */
.league-progress-bar {
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
border: 2px solid #dee2e6;
border-radius: 12px;
padding: 20px;
margin-bottom: 25px;
width: 100%;
box-sizing: border-box;
}
.progress-title {
font-size: 1.1rem;
font-weight: bold;
color: #333;
margin-bottom: 15px;
display: flex;
align-items: center;
gap: 8px;
}
.tournaments-progress-horizontal {
display: flex;
gap: 16px;
justify-content: center;
align-items: center;
flex-wrap: wrap;
position: relative;
}
.tournament-progress-dot {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 80px;
height: 80px;
border-radius: 12px;
border: 2.5px solid #dee2e6;
background: white;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
}
.tournament-progress-dot:hover {
transform: translateY(-3px);
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.12);
}
.tournament-progress-dot::after {
content: '→';
position: absolute;
right: -24px;
top: 50%;
transform: translateY(-50%);
font-size: 1.5rem;
color: #dee2e6;
font-weight: bold;
}
.tournament-progress-dot:last-child::after {
display: none;
}
.dot-number {
font-size: 1.6rem;
font-weight: bold;
color: #495057;
margin-bottom: 2px;
}
.dot-status {
font-size: 1.3rem;
line-height: 1;
}
/* Pending - Gray */
.tournament-progress-dot.pending {
background: #f8f9fa;
border-color: #dee2e6;
}
.tournament-progress-dot.pending .dot-number {
color: #adb5bd;
}
/* Active - Blue with animation */
.tournament-progress-dot.active {
background: linear-gradient(135deg, #cfe2ff 0%, #e7f1ff 100%);
border-color: #0056b3;
border-width: 3px;
box-shadow: 0 4px 16px rgba(0, 123, 255, 0.3);
}
.tournament-progress-dot.active .dot-number {
color: #0056b3;
animation: pulse-dot 1.5s infinite;
}
.tournament-progress-dot.active::after {
color: #007bff;
animation: pulse-arrow 1.5s infinite;
}
@keyframes pulse-dot {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.08); }
}
@keyframes pulse-arrow {
0%, 100% { transform: translateY(-50%) translateX(0); opacity: 0.6; }
50% { transform: translateY(-50%) translateX(4px); opacity: 1; }
}
/* Completed - Green */
.tournament-progress-dot.completed {
background: linear-gradient(135deg, #d4edda 0%, #e8f5e8 100%);
border-color: #1e7e34;
}
.tournament-progress-dot.completed .dot-number {
color: #1e7e34;
}
.tournament-progress-dot.completed::after {
color: #28a745;
}
/* Round Progress Timeline */
.rounds-progress-bar {
margin-top: 20px;
padding-top: 20px;
border-top: 2px solid #dee2e6;
}
.rounds-progress-title {
font-size: 0.95rem;
font-weight: 600;
color: #495057;
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 6px;
}
.rounds-progress-horizontal {
display: flex;
gap: 12px;
justify-content: center;
align-items: center;
flex-wrap: wrap;
position: relative;
}
.round-progress-dot {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 50px;
height: 50px;
border-radius: 10px;
border: 2px solid #dee2e6;
background: white;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
font-size: 0.85rem;
font-weight: bold;
}
.round-progress-dot:hover {
cursor: default;
}
.round-progress-dot::after {
content: '→';
position: absolute;
right: -18px;
top: 50%;
transform: translateY(-50%);
font-size: 1.2rem;
color: #dee2e6;
font-weight: bold;
}
.round-progress-dot:last-child::after {
display: none;
}
/* Round Pending - Gray */
.round-progress-dot.pending {
background: #f8f9fa;
border-color: #dee2e6;
color: #adb5bd;
}
/* Round Active - Blue */
.round-progress-dot.active {
background: linear-gradient(135deg, #cfe2ff 0%, #e7f1ff 100%);
border-color: #0056b3;
border-width: 2px;
color: #0056b3;
box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3);
}
.round-progress-dot.active::after {
color: #007bff;
}
/* Round Completed - Green */
.round-progress-dot.completed {
background: linear-gradient(135deg, #d4edda 0%, #e8f5e8 100%);
border-color: #1e7e34;
color: #1e7e34;
}
.round-progress-dot.completed::after {
color: #28a745;
}
/* Tournament Cards Layout */
.tournaments-progress-container {
margin-top: 30px;
}
.tournaments-grid {
display: grid;
grid-template-columns: 1fr;
gap: 15px;
margin-top: 20px;
max-width: 600px;
}
.tournament-card {
background: white;
border: 2px solid #dee2e6;
border-radius: 12px;
padding: 20px;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.tournament-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: #dee2e6;
transition: all 0.3s ease;
}
.tournament-card.pending {
opacity: 0.7;
background: #f8f9fa;
}
.tournament-card.pending::before {
background: linear-gradient(90deg, #6c757d 0%, #95a3b3 100%);
}
.tournament-card.active {
border-color: #007bff;
box-shadow: 0 4px 12px rgba(0, 123, 255, 0.2);
}
.tournament-card.active::before {
background: linear-gradient(90deg, #007bff 0%, #0056b3 100%);
}
.tournament-card.completed {
opacity: 0.8;
}
.tournament-card.completed::before {
background: linear-gradient(90deg, #28a745 0%, #20c997 100%);
}
.tournament-card-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 15px;
}
.tournament-card-number {
font-size: 1.4rem;
font-weight: bold;
color: #007bff;
background: #e7f1ff;
width: 50px;
height: 50px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.tournament-card.pending .tournament-card-number {
background: #e9ecef;
color: #6c757d;
}
.tournament-card.completed .tournament-card-number {
background: #d4edda;
color: #28a745;
}
.tournament-card-status {
display: inline-block;
padding: 4px 12px;
border-radius: 20px;
font-size: 0.85rem;
font-weight: bold;
text-align: center;
}
.tournament-card.pending .tournament-card-status {
background: #e9ecef;
color: #6c757d;
}
.tournament-card.active .tournament-card-status {
background: #cfe2ff;
color: #084298;
animation: pulse 2s infinite;
}
.tournament-card.completed .tournament-card-status {
background: #d4edda;
color: #155724;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.7; }
}
.tournament-card-info {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
margin-bottom: 15px;
}
.tournament-card-info-item {
background: #f8f9fa;
padding: 10px;
border-radius: 6px;
text-align: center;
}
.tournament-card-info-label {
font-size: 0.8rem;
color: #666;
margin-bottom: 4px;
}
.tournament-card-info-value {
font-size: 1.1rem;
font-weight: bold;
color: #333;
}
.tournament-card-actions {
display: flex;
gap: 10px;
flex-direction: column;
}
.tournament-card-btn {
padding: 10px 15px;
border: none;
border-radius: 6px;
cursor: pointer;
font-weight: bold;
font-size: 0.9rem;
transition: all 0.2s ease;
text-decoration: none;
text-align: center;
}
.tournament-card-btn.start {
background: #28a745;
color: white;
}
.tournament-card-btn.start:hover {
background: #218838;
transform: translateY(-2px);
}
.tournament-card-btn.disabled {
background: #e9ecef;
color: #6c757d;
cursor: not-allowed;
}
.tournament-card-btn.disabled:hover {
transform: none;
}
.tournament-card-check {
font-size: 2rem;
color: #28a745;
text-align: center;
margin: 10px 0;
}
.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: repeat(auto-fit, minmax(280px, 1fr));
gap: 15px;
}
.type-option {
background: white;
border: 2px solid #dee2e6;
border-radius: 12px;
padding: 25px;
cursor: pointer;
transition: all 0.3s ease;
text-align: center;
position: relative;
overflow: hidden;
}
.type-option::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
border-radius: 12px 12px 0 0;
background: #dee2e6;
transition: all 0.3s ease;
}
/* 4 Targets - Green theme */
.type-option[data-type="4_targets"] {
border-color: #11998e;
}
.type-option[data-type="4_targets"]:hover {
border-color: #0f766e;
box-shadow: 0 4px 15px rgba(17, 153, 142, 0.25);
transform: translateY(-2px);
}
.type-option[data-type="4_targets"].selected {
border-color: #0f766e;
background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);
}
.type-option[data-type="4_targets"]::before {
background: linear-gradient(90deg, #11998e 0%, #38ef7d 100%);
}
/* 20 Targets - Red theme */
.type-option[data-type="20_targets"] {
border-color: #ff6b6b;
}
.type-option[data-type="20_targets"]:hover {
border-color: #e53e3e;
box-shadow: 0 4px 15px rgba(255, 107, 107, 0.25);
transform: translateY(-2px);
}
.type-option[data-type="20_targets"].selected {
border-color: #e53e3e;
background: linear-gradient(135deg, #fff5f5 0%, #ffe8e8 100%);
}
.type-option[data-type="20_targets"]::before {
background: linear-gradient(90deg, #ff6b6b 0%, #ff8e53 100%);
}
/* 40 Targets - Blue theme */
.type-option[data-type="40_targets"] {
border-color: #667eea;
}
.type-option[data-type="40_targets"]:hover {
border-color: #5a67d8;
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.25);
transform: translateY(-2px);
}
.type-option[data-type="40_targets"].selected {
border-color: #5a67d8;
background: linear-gradient(135deg, #f8f9ff 0%, #e8ebff 100%);
}
.type-option[data-type="40_targets"]::before {
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
}
.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: 0.9rem;
font-weight: bold;
transition: all 0.2s ease;
min-width: 200px;
text-decoration: none;
font-family: Arial, sans-serif;
}
.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;
}
/* NEW PLAYER LIST STYLES */
.player-management-header {
display: flex;
gap: 15px;
align-items: center;
margin-bottom: 20px;
flex-wrap: wrap;
}
.search-container {
flex: 1;
min-width: 250px;
position: relative;
}
.search-input {
width: 100%;
padding: 12px 40px 12px 15px;
border: 2px solid #e9ecef;
border-radius: 8px;
font-size: 1rem;
transition: border-color 0.2s ease;
}
.search-input:focus {
outline: none;
border-color: #007bff;
}
.search-icon {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
color: #6c757d;
font-size: 1.1rem;
}
.player-controls {
display: flex;
gap: 10px;
align-items: center;
flex-wrap: wrap;
}
.filter-btn {
padding: 8px 16px;
border: 2px solid #e9ecef;
background: white;
border-radius: 6px;
cursor: pointer;
font-size: 0.9rem;
font-weight: 500;
transition: all 0.2s ease;
white-space: nowrap;
}
.filter-btn:hover {
border-color: #007bff;
background: #f8f9fa;
}
.filter-btn.active {
background: #007bff;
color: white;
border-color: #007bff;
}
.bulk-actions {
display: flex;
gap: 8px;
align-items: center;
}
.bulk-btn {
padding: 8px 12px;
border: 2px solid #e9ecef;
background: white;
border-radius: 6px;
cursor: pointer;
font-size: 0.85rem;
font-weight: 500;
transition: all 0.2s ease;
white-space: nowrap;
}
.bulk-btn:hover {
border-color: #28a745;
background: #f8fff9;
}
.bulk-btn.danger:hover {
border-color: #dc3545;
background: #fff5f5;
}
.add-player-section {
background: #f8f9fa;
border: 2px solid #e9ecef;
border-radius: 8px;
padding: 20px;
margin-bottom: 25px;
}
.add-player-form {
display: flex;
gap: 15px;
align-items: center;
}
.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;
}
/* Player Table/List */
.player-list-container {
background: white;
border: 1px solid #dee2e6;
border-radius: 8px;
overflow: hidden;
}
.player-table {
width: 100%;
border-collapse: collapse;
}
.player-table th,
.player-table td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #e9ecef;
}
.player-table th {
background: #f8f9fa;
font-weight: bold;
color: #495057;
position: sticky;
top: 0;
z-index: 10;
}
.player-table tr:hover {
background: #f8f9fa;
}
.player-table tr.enabled {
background: #f8fff9;
}
.player-table tr.enabled:hover {
background: #e8f5e8;
}
.player-table tr.disabled {
opacity: 0.6;
}
.player-id {
font-weight: bold;
color: #007bff;
min-width: 50px;
}
.player-name {
font-weight: 500;
color: #333;
min-width: 200px;
}
.player-name.editing {
padding: 0;
}
.player-name input {
width: 100%;
padding: 8px 12px;
border: 2px solid #007bff;
border-radius: 4px;
font-size: 1rem;
font-weight: 500;
}
.player-status {
min-width: 120px;
}
.status-badge {
display: inline-block;
padding: 4px 12px;
border-radius: 12px;
font-size: 0.85rem;
font-weight: bold;
}
.status-enabled {
background: #d4edda;
color: #155724;
}
.status-disabled {
background: #f8d7da;
color: #721c24;
}
.player-actions {
min-width: 160px;
}
.action-toggle {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 8px;
}
.toggle-switch {
position: relative;
width: 50px;
height: 24px;
background: #ccc;
border-radius: 12px;
cursor: pointer;
transition: background-color 0.2s ease;
}
.toggle-switch.enabled {
background: #28a745;
}
.toggle-switch::after {
content: '';
position: absolute;
top: 2px;
left: 2px;
width: 20px;
height: 20px;
background: white;
border-radius: 50%;
transition: transform 0.2s ease;
}
.toggle-switch.enabled::after {
transform: translateX(26px);
}
.action-buttons-cell {
display: flex;
gap: 6px;
}
.action-btn-small {
padding: 6px 10px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.85rem;
font-weight: 500;
transition: all 0.2s ease;
}
.edit-btn-small {
background: #ffc107;
color: white;
}
.edit-btn-small:hover {
background: #e0a800;
}
.delete-btn-small {
background: #dc3545;
color: white;
}
.delete-btn-small:hover {
background: #c82333;
}
.stats-summary {
display: flex;
gap: 20px;
align-items: center;
margin-bottom: 15px;
padding: 15px;
background: #f8f9fa;
border-radius: 6px;
flex-wrap: wrap;
}
.stat-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 0.9rem;
}
.stat-number {
font-weight: bold;
font-size: 1.1rem;
}
.stat-enabled {
color: #28a745;
}
.stat-disabled {
color: #dc3545;
}
.stat-total {
color: #007bff;
}
.no-results {
text-align: center;
padding: 40px 20px;
color: #6c757d;
font-style: italic;
}
/* 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;
}
/* Responsive unified layout */
@media (max-width: 1024px) {
.league-tournament-container {
grid-template-columns: 1fr;
gap: 20px;
}
}
/* 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-management-header {
flex-direction: column;
align-items: stretch;
}
.search-container {
min-width: auto;
}
.player-controls,
.bulk-actions {
justify-content: center;
}
.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;
}
.stats-summary {
flex-direction: column;
align-items: stretch;
gap: 10px;
}
.player-table {
font-size: 0.9rem;
}
.player-table th,
.player-table td {
padding: 8px 10px;
}
.action-buttons-cell {
flex-direction: column;
gap: 4px;
}
}
/* Hide table columns on small screens */
/* Responsive Progress Bar */
@media (max-width: 1200px) {
.tournament-progress-dot {
width: 70px;
height: 70px;
}
.dot-number {
font-size: 1.3rem;
}
.dot-status {
font-size: 1.1rem;
}
.tournament-progress-dot::after {
font-size: 1.3rem;
right: -20px;
}
.tournaments-progress-horizontal {
gap: 14px;
}
}
@media (max-width: 768px) {
.league-progress-bar {
padding: 16px;
margin-bottom: 20px;
}
.progress-title {
font-size: 1rem;
margin-bottom: 12px;
}
.tournament-progress-dot {
width: 65px;
height: 65px;
}
.dot-number {
font-size: 1.2rem;
}
.dot-status {
font-size: 1rem;
}
.tournament-progress-dot::after {
font-size: 1.2rem;
right: -18px;
}
.tournaments-progress-horizontal {
gap: 12px;
}
}
@media (max-width: 600px) {
.player-table .actions-col {
display: none;
}
.player-table .status-col {
display: none;
}
.league-progress-bar {
padding: 12px;
margin-bottom: 16px;
}
.progress-title {
font-size: 0.95rem;
margin-bottom: 10px;
}
.tournament-progress-dot {
width: 60px;
height: 60px;
}
.dot-number {
font-size: 1.1rem;
margin-bottom: 1px;
}
.dot-status {
font-size: 0.9rem;
}
.tournament-progress-dot::after {
font-size: 1rem;
right: -16px;
}
.tournaments-progress-horizontal {
gap: 10px;
}
}
</style>
<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="/" class="nav-btn">📺 <span data-i18n="navigation.dashboard">Dashboard</span></a>
<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">📋 <span data-i18n="tournament.view_draft">Draft</span></a>
{% endif %}
{% if tournament_state %}
<a href="/results/calculator" class="nav-btn">🎯 <span data-i18n="navigation.calculator">Results Calculator</span></a>
{% endif %}
</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" style="grid-template-columns: 1fr;">
<!-- 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>
</div>
<!-- Tournament Card (only shown when tournament is active) -->
{% if tournament_state %}
<div class="league-tournament-card tournament" style="grid-column: 1;">
<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>
{% endif %}
</div>
<!-- Joker Selection & Start Next Tournament -->
<div class="league-status">
{% 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">🃏 <span data-i18n="league.joker_selection_for_tournament">Izbira Jokerja za Turnir</span> {{ league_state.current_tournament + 1 }}</div>
<p style="margin-bottom: 15px; color: #856404;" 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-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()">
🚀 <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>
{% else %}
<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 %}
{% 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" 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" style="grid-template-columns: 1fr;">
<!-- 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 %}
<h2 class="section-title" data-i18n="league.setup">🏁 Nastavitev</h2>
<div class="league-inactive" data-i18n="league.no_active_league_tournament">Ni Aktivne Lige ali Turnirja</div>
<!-- Tournament Type Selection -->
<div class="tournament-type-selection">
<div class="type-title" data-i18n="league.select_tournament_type">Izberi Tip Turnirja</div>
<div class="type-options">
<div class="type-option" data-type="4_targets" onclick="selectTournamentType('4_targets')">
<input type="radio" name="tournament_type" value="4_targets">
<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 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-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 class="type-option" data-type="40_targets" onclick="selectTournamentType('40_targets')">
<input type="radio" name="tournament_type" value="40_targets">
<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>
<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>
{% endif %}
</div>
<!-- Player Management Section - NEW LIST FORMAT -->
{% if not league_state and not tournament_state %}
<div class="section">
<h2 class="section-title" data-i18n="players.player_management">👥 Upravljanje Igralcev</h2>
<!-- Add Player Section -->
<div class="add-player-section">
<h3 style="margin: 0 0 15px 0; color: #333;" data-i18n="league.add_new_player">Dodaj Novega Igralca</h3>
<div class="add-player-form">
<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 Igralca</span>
</button>
</div>
</div>
<!-- Search and Controls -->
<div class="player-management-header">
<div class="search-container">
<input type="text"
class="search-input"
id="playerSearch"
data-i18n="[placeholder]league.search_players_placeholder" placeholder="Išči igralce po imenu..."
oninput="filterPlayers()">
<span class="search-icon">🔍</span>
</div>
<div class="player-controls">
<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">Omogočen</button>
<button class="filter-btn" data-filter="disabled" onclick="setFilter('disabled')" data-i18n="players.disabled">Onemogočen</button>
</div>
<div class="bulk-actions">
<button class="bulk-btn" onclick="selectAllVisible()" data-i18n="general.select_all">Izberi Vse</button>
<button class="bulk-btn" onclick="enableSelected()" data-i18n="general.enable_selected">Omogoči Izbrane</button>
<button class="bulk-btn danger" onclick="disableSelected()" data-i18n="general.disable_selected">Onemogoči Izbrane</button>
</div>
</div>
<!-- Stats Summary -->
<div class="stats-summary">
<div class="stat-item">
<span class="stat-number stat-total" id="totalPlayersCount">0</span>
<span data-i18n="players.total_players">Skupaj Igralcev</span>
</div>
<div class="stat-item">
<span class="stat-number stat-enabled" id="enabledPlayersCount">0</span>
<span data-i18n="players.enabled">Omogočeni</span>
</div>
<div class="stat-item">
<span class="stat-number stat-disabled" id="disabledPlayersCount">0</span>
<span data-i18n="players.disabled">Onemogočeni</span>
</div>
<div class="stat-item">
<span class="stat-number" id="visiblePlayersCount">0</span>
<span data-i18n="general.visible">Vidni</span>
</div>
</div>
<!-- Player List Table -->
<div class="player-list-container">
<table class="player-table">
<thead>
<tr>
<th style="width: 40px;">
<input type="checkbox" id="selectAllCheckbox" onchange="toggleSelectAll()">
</th>
<th style="width: 60px;" data-i18n="league.id">ID</th>
<th data-i18n="league.name">Ime</th>
<th class="status-col" style="width: 120px;" data-i18n="general.status">Status</th>
<th class="actions-col" style="width: 200px;" data-i18n="general.actions">Dejanja</th>
</tr>
</thead>
<tbody id="playerTableBody">
<!-- Players will be populated here -->
</tbody>
</table>
<div class="no-results" id="noResults" style="display: none;">
<span data-i18n="league.no_players_found">Ni najdenih igralcev, ki bi ustrezali kriterijem iskanja.</span>
</div>
</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';
let currentFilter = 'all';
let searchTerm = '';
let selectedPlayers = new Set();
// 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;
}
// 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;
document.getElementById('totalPlayersCount').textContent = totalCount;
document.getElementById('enabledPlayersCount').textContent = enabledCount;
document.getElementById('disabledPlayersCount').textContent = disabledCount;
document.getElementById('visiblePlayersCount').textContent = visibleCount;
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';
}
}
}
// Render the player table
function renderPlayerTable() {
const tbody = document.getElementById('playerTableBody');
const noResults = document.getElementById('noResults');
// Check if playerTableBody exists before proceeding (it may not exist on all pages)
if (!tbody) {
return;
}
const filteredPlayers = getFilteredPlayers();
tbody.innerHTML = '';
if (filteredPlayers.length === 0) {
if (noResults) {
noResults.style.display = 'block';
}
return;
} else {
if (noResults) {
noResults.style.display = 'none';
}
}
filteredPlayers.forEach(player => {
const row = document.createElement('tr');
row.setAttribute('data-player-id', player.id);
row.className = player.enabled ? 'enabled' : 'disabled';
row.innerHTML = `
<td>
<input type="checkbox"
${selectedPlayers.has(player.id) ? 'checked' : ''}
onchange="togglePlayerSelection(${player.id}, this.checked)">
</td>
<td class="player-id">#${player.id}</td>
<td class="player-name" id="playerName${player.id}">
${player.name}
</td>
<td class="player-status status-col">
<span class="status-badge ${player.enabled ? 'status-enabled' : 'status-disabled'}">
${player.enabled ? '✓' : '✗'}
</span>
</td>
<td class="player-actions actions-col">
<div class="action-toggle">
<div class="toggle-switch ${player.enabled ? 'enabled' : ''}"
onclick="togglePlayer(${player.id})"></div>
<span style="font-size: 0.9rem; color: #666;">
${player.enabled ? t('league.competing') : t('league.not_competing')}
</span>
</div>
<div class="action-buttons-cell">
<button class="action-btn-small edit-btn-small"
onclick="editPlayer(${player.id})"
title="Edit Player Name">
✏️ <span data-i18n="league.edit">Uredi</span>
</button>
<button class="action-btn-small delete-btn-small"
onclick="confirmDeletePlayer(${player.id})"
title="Delete Player">
🗑️ <span data-i18n="general.delete">Delete</span>
</button>
</div>
</td>
`;
tbody.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;
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;
renderPlayerTable();
}
}
} else {
// Start editing
editingPlayer = playerId;
nameElement.className = 'player-name editing';
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;
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();
}
}
// Toggle player enabled/disabled
// Update a single player row without re-rendering the entire table
function updatePlayerRow(playerId) {
const player = players.find(p => p.id === playerId);
if (!player) return;
const row = document.querySelector(`tr[data-player-id="${playerId}"]`);
if (!row) return;
// Update row class (enabled/disabled)
row.className = player.enabled ? 'enabled' : 'disabled';
// Update status badge
const statusBadge = row.querySelector('.status-badge');
if (statusBadge) {
statusBadge.className = `status-badge ${player.enabled ? 'status-enabled' : 'status-disabled'}`;
statusBadge.textContent = player.enabled ? '✓' : '✗';
}
// Update toggle switch
const toggleSwitch = row.querySelector('.toggle-switch');
if (toggleSwitch) {
toggleSwitch.className = `toggle-switch ${player.enabled ? 'enabled' : ''}`;
}
// Update competing/not competing text using translation function if available
const statusText = row.querySelector('.action-toggle span');
if (statusText) {
// Try to use the translation function if available, otherwise use hardcoded text
if (typeof t === 'function' && currentTranslations && Object.keys(currentTranslations).length > 0) {
statusText.textContent = player.enabled ? t('league.competing') : t('league.not_competing');
} else {
// Fallback to hardcoded translations
if (currentLanguage === 'sl') {
statusText.textContent = player.enabled ? 'Tekmuje' : 'Ne tekmuje';
} else {
statusText.textContent = player.enabled ? 'Competing' : 'Not competing';
}
}
}
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() {
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.');
}
}
// 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>