baeaad0d49
- Merged remote changes: Liga krog3 and league state management - Resolved merge conflicts in locale files and templates - Kept local v1.0.1 version and styling improvements - Added league combine functionality from remote - Preserved new league_state.json and tournament_results.json - Integrated i18n improvements Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2706 lines
76 KiB
HTML
2706 lines
76 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">
|
||
<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 #28a745;
|
||
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 #28a745;
|
||
}
|
||
|
||
.league-tournament-card.league h3 {
|
||
border-bottom-color: #28a745;
|
||
}
|
||
|
||
.league-tournament-card.tournament h3 {
|
||
border-bottom-color: #28a745;
|
||
}
|
||
|
||
.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: #1e7e34;
|
||
border-width: 3px;
|
||
box-shadow: 0 4px 16px rgba(0, 123, 255, 0.3);
|
||
}
|
||
|
||
.tournament-progress-dot.active .dot-number {
|
||
color: #1e7e34;
|
||
animation: pulse-dot 1.5s infinite;
|
||
}
|
||
|
||
.tournament-progress-dot.active::after {
|
||
color: #28a745;
|
||
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: #1e7e34;
|
||
border-width: 2px;
|
||
color: #1e7e34;
|
||
box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3);
|
||
}
|
||
|
||
.round-progress-dot.active::after {
|
||
color: #28a745;
|
||
}
|
||
|
||
/* 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: #28a745;
|
||
box-shadow: 0 4px 12px rgba(0, 123, 255, 0.2);
|
||
}
|
||
|
||
.tournament-card.active::before {
|
||
background: linear-gradient(90deg, #28a745 0%, #1e7e34 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: #28a745;
|
||
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: #28a745;
|
||
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 !important;
|
||
font-family: Arial, sans-serif;
|
||
display: inline-block;
|
||
}
|
||
|
||
.action-btn:hover {
|
||
background: #1e7e34;
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 4px 8px rgba(0, 123, 255, 0.3);
|
||
text-decoration: none !important;
|
||
}
|
||
|
||
.action-btn:focus,
|
||
.action-btn:active,
|
||
.action-btn:visited {
|
||
text-decoration: none !important;
|
||
}
|
||
|
||
.action-btn:disabled {
|
||
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;
|
||
}
|
||
|
||
.action-btn.info {
|
||
background: #007bff;
|
||
border-color: #0056b3;
|
||
}
|
||
|
||
.action-btn.info:hover {
|
||
background: #0056b3;
|
||
box-shadow: 0 4px 8px rgba(0, 123, 255, 0.4);
|
||
}
|
||
|
||
.player-count {
|
||
margin: 20px 0;
|
||
font-size: 1.2rem;
|
||
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: #28a745;
|
||
}
|
||
|
||
.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: #28a745;
|
||
background: #f8f9fa;
|
||
}
|
||
|
||
.filter-btn.active {
|
||
background: #28a745;
|
||
color: white;
|
||
border-color: #28a745;
|
||
}
|
||
|
||
.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: #28a745;
|
||
}
|
||
|
||
.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: #28a745;
|
||
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 #28a745;
|
||
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: #28a745;
|
||
}
|
||
|
||
.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 checked{% endif %}
|
||
data-player-id="{{ player_id }}">
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="action-buttons">
|
||
{% 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>
|
||
{% 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();
|
||
|
||
// Open main page in new tab for TV display
|
||
window.open('/', '_blank', 'noopener,noreferrer');
|
||
|
||
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();
|
||
|
||
// Open main page in new tab for TV display
|
||
window.open('/', '_blank', 'noopener,noreferrer');
|
||
|
||
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> |