1952 lines
53 KiB
HTML
1952 lines
53 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<title>Tournament Management</title>
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<style>
|
||
* {
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
html, body {
|
||
margin: 0;
|
||
padding: 0;
|
||
background: #f5f5f5;
|
||
font-family: Arial, sans-serif;
|
||
min-height: 100vh;
|
||
}
|
||
|
||
.navbar {
|
||
background: white;
|
||
color: black;
|
||
padding: 15px 25px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
border-bottom: 2px solid #ccc;
|
||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.navbar-title {
|
||
font-size: 1.8rem;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
|
||
.navbar-controls {
|
||
display: flex;
|
||
gap: 12px;
|
||
align-items: center;
|
||
}
|
||
|
||
.nav-btn {
|
||
background: #f8f9fa;
|
||
border: 2px solid #e9ecef;
|
||
cursor: pointer;
|
||
padding: 12px 20px;
|
||
border-radius: 8px;
|
||
transition: all 0.2s ease;
|
||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||
color: #333;
|
||
text-decoration: none;
|
||
font-weight: bold;
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.nav-btn:hover {
|
||
background: #e9ecef;
|
||
border-color: #007bff;
|
||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
|
||
transform: translateY(-1px);
|
||
color: #007bff;
|
||
}
|
||
|
||
.nav-btn.primary {
|
||
background: #007bff;
|
||
border-color: #0056b3;
|
||
color: white;
|
||
}
|
||
|
||
.nav-btn.primary:hover {
|
||
background: #0056b3;
|
||
color: white;
|
||
}
|
||
|
||
.nav-btn.success {
|
||
background: #28a745;
|
||
border-color: #1e7e34;
|
||
color: white;
|
||
}
|
||
|
||
.nav-btn.success:hover {
|
||
background: #1e7e34;
|
||
color: white;
|
||
}
|
||
|
||
.nav-btn.danger {
|
||
background: #dc3545;
|
||
border-color: #c82333;
|
||
color: white;
|
||
}
|
||
|
||
.nav-btn.danger:hover {
|
||
background: #c82333;
|
||
color: white;
|
||
}
|
||
|
||
.container {
|
||
max-width: 1200px;
|
||
margin: 0 auto;
|
||
padding: 30px 20px;
|
||
}
|
||
|
||
.section {
|
||
background: white;
|
||
border-radius: 12px;
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||
padding: 25px;
|
||
margin-bottom: 30px;
|
||
}
|
||
|
||
.section-title {
|
||
font-size: 1.5rem;
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-bottom: 20px;
|
||
border-bottom: 3px solid #007bff;
|
||
padding-bottom: 8px;
|
||
}
|
||
|
||
/* League Status */
|
||
.league-status {
|
||
text-align: center;
|
||
padding: 30px;
|
||
}
|
||
|
||
.league-active {
|
||
color: #28a745;
|
||
font-size: 1.4rem;
|
||
font-weight: bold;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.league-inactive {
|
||
color: #6c757d;
|
||
font-size: 1.2rem;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.league-info {
|
||
background: #f8f9fa;
|
||
border: 1px solid #dee2e6;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
margin: 20px 0;
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||
gap: 15px;
|
||
}
|
||
|
||
.info-item {
|
||
background: white;
|
||
padding: 15px;
|
||
border-radius: 6px;
|
||
border: 1px solid #e9ecef;
|
||
text-align: center;
|
||
}
|
||
|
||
.info-label {
|
||
font-size: 0.9rem;
|
||
color: #666;
|
||
margin-bottom: 5px;
|
||
}
|
||
|
||
.info-value {
|
||
font-size: 1.3rem;
|
||
font-weight: bold;
|
||
color: #333;
|
||
}
|
||
|
||
/* Tournament Type Selection */
|
||
.tournament-type-selection {
|
||
background: #f8f9fa;
|
||
border: 2px solid #e9ecef;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
margin: 20px 0;
|
||
}
|
||
|
||
.type-title {
|
||
font-size: 1.1rem;
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-bottom: 15px;
|
||
}
|
||
|
||
.type-options {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
||
gap: 15px;
|
||
}
|
||
|
||
.type-option {
|
||
background: white;
|
||
border: 2px solid #dee2e6;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
cursor: pointer;
|
||
transition: all 0.2s ease;
|
||
text-align: center;
|
||
}
|
||
|
||
.type-option:hover {
|
||
border-color: #007bff;
|
||
box-shadow: 0 2px 8px rgba(0, 123, 255, 0.15);
|
||
}
|
||
|
||
.type-option.selected {
|
||
border-color: #007bff;
|
||
background: #f0f8ff;
|
||
}
|
||
|
||
.type-option input[type="radio"] {
|
||
margin-right: 10px;
|
||
width: 18px;
|
||
height: 18px;
|
||
}
|
||
|
||
.type-name {
|
||
font-size: 1.2rem;
|
||
font-weight: bold;
|
||
color: #333;
|
||
margin-bottom: 8px;
|
||
}
|
||
|
||
.type-description {
|
||
font-size: 0.9rem;
|
||
color: #666;
|
||
line-height: 1.4;
|
||
}
|
||
|
||
/* Joker Management */
|
||
.joker-section {
|
||
background: #fff3cd;
|
||
border: 2px solid #ffeaa7;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
margin: 20px 0;
|
||
}
|
||
|
||
.joker-title {
|
||
font-size: 1.1rem;
|
||
font-weight: bold;
|
||
color: #856404;
|
||
margin-bottom: 15px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
}
|
||
|
||
.joker-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: 15px;
|
||
}
|
||
|
||
.joker-player {
|
||
background: white;
|
||
border: 1px solid #dee2e6;
|
||
border-radius: 6px;
|
||
padding: 15px;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
}
|
||
|
||
.joker-player.used {
|
||
opacity: 0.6;
|
||
background: #f8f9fa;
|
||
}
|
||
|
||
.joker-checkbox {
|
||
width: 18px;
|
||
height: 18px;
|
||
}
|
||
|
||
.joker-checkbox:disabled {
|
||
cursor: not-allowed;
|
||
}
|
||
|
||
/* Action Buttons */
|
||
.action-buttons {
|
||
display: flex;
|
||
gap: 15px;
|
||
flex-wrap: wrap;
|
||
justify-content: center;
|
||
margin: 20px 0;
|
||
}
|
||
|
||
.action-btn {
|
||
background: #007bff;
|
||
border: none;
|
||
color: white;
|
||
padding: 15px 30px;
|
||
border-radius: 8px;
|
||
cursor: pointer;
|
||
font-size: 1.1rem;
|
||
font-weight: bold;
|
||
transition: all 0.2s ease;
|
||
min-width: 200px;
|
||
text-decoration: none;
|
||
}
|
||
|
||
.action-btn:hover {
|
||
background: #0056b3;
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 4px 8px rgba(0, 123, 255, 0.3);
|
||
}
|
||
|
||
.action-btn:disabled {
|
||
background: #6c757d;
|
||
cursor: not-allowed;
|
||
transform: none;
|
||
box-shadow: none;
|
||
}
|
||
|
||
.action-btn.success {
|
||
background: #28a745;
|
||
}
|
||
|
||
.action-btn.success:hover {
|
||
background: #1e7e34;
|
||
}
|
||
|
||
.action-btn.danger {
|
||
background: #dc3545;
|
||
}
|
||
|
||
.action-btn.danger:hover {
|
||
background: #c82333;
|
||
}
|
||
|
||
.player-count {
|
||
margin: 20px 0;
|
||
font-size: 1.2rem;
|
||
color: #333;
|
||
text-align: center;
|
||
}
|
||
|
||
.enabled-count {
|
||
color: #28a745;
|
||
font-weight: bold;
|
||
font-size: 1.4rem;
|
||
}
|
||
|
||
.warning {
|
||
background: #fff3cd;
|
||
border: 1px solid #ffeaa7;
|
||
color: #856404;
|
||
padding: 12px 16px;
|
||
border-radius: 6px;
|
||
margin: 15px 0;
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* Tournament Status */
|
||
.tournament-status {
|
||
text-align: center;
|
||
padding: 30px;
|
||
}
|
||
|
||
.tournament-active {
|
||
color: #28a745;
|
||
font-size: 1.4rem;
|
||
font-weight: bold;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.tournament-inactive {
|
||
color: #6c757d;
|
||
font-size: 1.2rem;
|
||
margin-bottom: 20px;
|
||
}
|
||
|
||
.tournament-info {
|
||
background: #f8f9fa;
|
||
border: 1px solid #dee2e6;
|
||
border-radius: 8px;
|
||
padding: 20px;
|
||
margin: 20px 0;
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||
gap: 15px;
|
||
}
|
||
|
||
/* 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;
|
||
}
|
||
|
||
/* 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 */
|
||
@media (max-width: 600px) {
|
||
.player-table .actions-col {
|
||
display: none;
|
||
}
|
||
|
||
.player-table .status-col {
|
||
display: none;
|
||
}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="navbar">
|
||
<div class="navbar-title">🏆 Tournament Management</div>
|
||
<div class="navbar-controls">
|
||
<a href="/" class="nav-btn">← Dashboard</a>
|
||
{% if league_state and not league_state.league_finished %}
|
||
<a href="/results/calculator" class="nav-btn primary">🎯 Results Calculator</a>
|
||
<button class="nav-btn danger" onclick="resetLeague()">🗑️ Reset League</button>
|
||
{% elif tournament_state %}
|
||
<a href="/tournament/draft" class="nav-btn">📋 View Draft</a>
|
||
<a href="/results/calculator" class="nav-btn primary">🎯 Results Calculator</a>
|
||
<button class="nav-btn danger" onclick="resetTournament()">🗑️ Reset Tournament</button>
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="container">
|
||
<!-- League/Tournament Management Section -->
|
||
<div class="section">
|
||
{% if league_state and not league_state.league_finished %}
|
||
<h2 class="section-title">🏆 League Management</h2>
|
||
<div class="league-status">
|
||
<div class="league-active">🏆 League Active</div>
|
||
<div class="league-info">
|
||
<div class="info-item">
|
||
<div class="info-label">Tournament Type</div>
|
||
<div class="info-value">
|
||
{% if league_state.tournament_type == '40_targets' %}
|
||
40 Targets
|
||
{% elif league_state.tournament_type == '4_targets' %}
|
||
4 Targets
|
||
{% else %}
|
||
20 Targets
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
<div class="info-item">
|
||
<div class="info-label">Current Tournament</div>
|
||
<div class="info-value">{{ league_state.current_tournament }} / {{ league_state.total_tournaments }}</div>
|
||
</div>
|
||
<div class="info-item">
|
||
<div class="info-label">Participants</div>
|
||
<div class="info-value">{{ league_state.participants|length }}</div>
|
||
</div>
|
||
<div class="info-item">
|
||
<div class="info-label">Completed</div>
|
||
<div class="info-value">{{ league_state.completed_tournaments|length }}</div>
|
||
</div>
|
||
<div class="info-item">
|
||
<div class="info-label">Created</div>
|
||
<div class="info-value">{{ league_state.created_at[:10] }}</div>
|
||
</div>
|
||
</div>
|
||
|
||
{% if tournament_state %}
|
||
{% else %}
|
||
{% if league_state.current_tournament < league_state.total_tournaments %}
|
||
<!-- Joker selection -->
|
||
<div class="joker-section" id="jokerSection">
|
||
<div class="joker-title">🃏 Joker Selection for Tournament {{ league_state.current_tournament + 1 }}</div>
|
||
<p style="margin-bottom: 15px; color: #856404;">Select players who will use their joker (skip this tournament). Each player can only use their joker once per league.</p>
|
||
|
||
<div class="joker-grid">
|
||
{% for player_id, participant in league_state.participants.items() %}
|
||
<div class="joker-player {% if participant.joker_used %}used{% endif %}">
|
||
<span>{{ participant.name }}</span>
|
||
<input type="checkbox"
|
||
class="joker-checkbox"
|
||
id="joker_{{ player_id }}"
|
||
{% if participant.joker_used %}disabled{% endif %}
|
||
data-player-id="{{ player_id }}">
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
</div>
|
||
|
||
<div class="action-buttons">
|
||
<button class="action-btn success" onclick="startNextTournament()">
|
||
🚀 Start Tournament {{ league_state.current_tournament + 1 }}
|
||
</button>
|
||
</div>
|
||
{% else %}
|
||
<div class="warning">
|
||
<strong>League Complete!</strong> All tournaments scheduled. Finish current one to see final results.
|
||
</div>
|
||
{% endif %}
|
||
{% endif %}
|
||
</div>
|
||
|
||
{% elif league_state and league_state.league_finished %}
|
||
<h2 class="section-title">🏁 League Completed</h2>
|
||
<div class="league-status">
|
||
<div class="league-active">🏆 League Completed!</div>
|
||
<div class="league-info">
|
||
<div class="info-item">
|
||
<div class="info-label">Tournament Type</div>
|
||
<div class="info-value">
|
||
{% 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">Participants</div>
|
||
<div class="info-value">{{ league_state.participants|length }}</div>
|
||
</div>
|
||
<div class="info-item">
|
||
<div class="info-label">Tournaments</div>
|
||
<div class="info-value">{{ league_state.total_tournaments }}</div>
|
||
</div>
|
||
<div class="info-item">
|
||
<div class="info-label">Finished</div>
|
||
<div class="info-value">{{ league_state.finished_at[:10] if league_state.finished_at else 'Today' }}</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="action-buttons">
|
||
<a href="/results" class="action-btn success">🏆 View League Results</a>
|
||
<button class="action-btn danger" onclick="resetLeague()">🗑️ Reset League</button>
|
||
</div>
|
||
</div>
|
||
|
||
{% elif not league_state and tournament_state %}
|
||
<h2 class="section-title">🎯 Single Tournament Management</h2>
|
||
<div class="tournament-status">
|
||
<div class="tournament-active">🎯 Tournament Active</div>
|
||
<div class="league-info">
|
||
<div class="info-item">
|
||
<div class="info-label">Tournament Type</div>
|
||
<div class="info-value">
|
||
{% if tournament_state.tournament_type == '40_targets' %}
|
||
40 Targets
|
||
{% elif tournament_state.tournament_type == '4_targets' %}
|
||
4 Targets
|
||
{% else %}
|
||
20 Targets
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
<div class="info-item">
|
||
<div class="info-label">Players</div>
|
||
<div class="info-value">{{ tournament_state.total_players }}</div>
|
||
</div>
|
||
<div class="info-item">
|
||
<div class="info-label">Rounds</div>
|
||
<div class="info-value">{{ tournament_state.total_rounds }}</div>
|
||
</div>
|
||
<div class="info-item">
|
||
<div class="info-label">Current Round</div>
|
||
<div class="info-value">{{ tournament_state.current_round }}</div>
|
||
</div>
|
||
<div class="info-item">
|
||
<div class="info-label">Created</div>
|
||
<div class="info-value">{{ tournament_state.created_at[:10] if tournament_state.created_at else 'Today' }}</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="action-buttons">
|
||
<a href="/tournament/draft" class="action-btn">📋 View Draft</a>
|
||
<a href="/results/calculator" class="action-btn success">🎯 Score Tournament</a>
|
||
<a href="/" class="action-btn">📺 Dashboard</a>
|
||
<button class="action-btn danger" onclick="resetTournament()">🗑️ Reset Tournament</button>
|
||
</div>
|
||
</div>
|
||
|
||
{% else %}
|
||
<h2 class="section-title">🏁 Setup</h2>
|
||
<div class="league-inactive">No Active League or Tournament</div>
|
||
|
||
<!-- Tournament Type Selection -->
|
||
<div class="tournament-type-selection">
|
||
<div class="type-title">🎯 Select Tournament Type</div>
|
||
<div class="type-options">
|
||
<div class="type-option" onclick="selectTournamentType('4_targets')">
|
||
<input type="radio" name="tournament_type" value="4_targets">
|
||
<div class="type-name">4 Targets</div>
|
||
<div class="type-description">Quick format with 4 targets, 5 shots each (20 shots total)</div>
|
||
</div>
|
||
<div class="type-option selected" onclick="selectTournamentType('20_targets')">
|
||
<input type="radio" name="tournament_type" value="20_targets" checked>
|
||
<div class="type-name">20 Targets</div>
|
||
<div class="type-description">Standard format with 20 targets, 2 shots each (40 shots total)</div>
|
||
</div>
|
||
<div class="type-option" onclick="selectTournamentType('40_targets')">
|
||
<input type="radio" name="tournament_type" value="40_targets">
|
||
<div class="type-name">40 Targets</div>
|
||
<div class="type-description">Extended format with 40 targets, 2 shots each (80 shots total)</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="player-count">
|
||
<span class="enabled-count" id="enabledCount">0</span> players enabled
|
||
</div>
|
||
|
||
<div class="action-buttons">
|
||
<button class="action-btn success" id="startLeagueBtn" onclick="startLeague()">🏆 Start League (6 Tournaments)</button>
|
||
<button class="action-btn" id="startSingleBtn" onclick="startSingleTournament()">🎯 Start Single Tournament</button>
|
||
</div>
|
||
|
||
<div class="warning" id="warningMessage" style="display: none;">
|
||
<strong>Note:</strong> You need at least 1 enabled player to start.
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<!-- Current Tournament Status (if active) -->
|
||
{% if tournament_state and league_state %}
|
||
<div class="section">
|
||
<h2 class="section-title">📋 Current Tournament</h2>
|
||
|
||
<div class="tournament-status">
|
||
<div class="tournament-active">🎯 Tournament Active</div>
|
||
<div class="tournament-info">
|
||
<div class="info-item">
|
||
<div class="info-label">Tournament Type</div>
|
||
<div class="info-value">
|
||
{% if tournament_state.tournament_type == '40_targets' %}
|
||
40 Targets
|
||
{% elif tournament_state.tournament_type == '4_targets' %}
|
||
4 Targets
|
||
{% else %}
|
||
20 Targets
|
||
{% endif %}
|
||
</div>
|
||
</div>
|
||
<div class="info-item">
|
||
<div class="info-label">Total Players</div>
|
||
<div class="info-value">{{ tournament_state.total_players }}</div>
|
||
</div>
|
||
<div class="info-item">
|
||
<div class="info-label">Total Rounds</div>
|
||
<div class="info-value">{{ tournament_state.total_rounds }}</div>
|
||
</div>
|
||
<div class="info-item">
|
||
<div class="info-label">Current Round</div>
|
||
<div class="info-value">{{ tournament_state.current_round }}</div>
|
||
</div>
|
||
{% if league_state %}
|
||
<div class="info-item">
|
||
<div class="info-label">League Tournament</div>
|
||
<div class="info-value">{{ tournament_state.league_tournament_number or 'N/A' }}</div>
|
||
</div>
|
||
{% endif %}
|
||
</div>
|
||
|
||
<div class="action-buttons">
|
||
<a href="/tournament/draft" class="action-btn">📋 View Draft</a>
|
||
<a href="/results/calculator" class="action-btn success">🎯 Score Tournament</a>
|
||
<a href="/" class="action-btn">📺 Dashboard</a>
|
||
<button class="action-btn danger" onclick="resetTournament()">🗑️ Reset Tournament</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
{% endif %}
|
||
|
||
<!-- Player Management Section - NEW LIST FORMAT -->
|
||
{% if not league_state and not tournament_state %}
|
||
<div class="section">
|
||
<h2 class="section-title">👥 Player Management</h2>
|
||
|
||
<!-- Add Player Section -->
|
||
<div class="add-player-section">
|
||
<h3 style="margin: 0 0 15px 0; color: #333;">Add New Player</h3>
|
||
<div class="add-player-form">
|
||
<input type="text"
|
||
id="newPlayerName"
|
||
placeholder="Enter player name..."
|
||
maxlength="50"
|
||
onkeypress="handleAddPlayerKeypress(event)">
|
||
<button class="add-btn" id="addPlayerBtn" onclick="addPlayer()">
|
||
➕ Add Player
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Search and Controls -->
|
||
<div class="player-management-header">
|
||
<div class="search-container">
|
||
<input type="text"
|
||
class="search-input"
|
||
id="playerSearch"
|
||
placeholder="Search players by name..."
|
||
oninput="filterPlayers()">
|
||
<span class="search-icon">🔍</span>
|
||
</div>
|
||
|
||
<div class="player-controls">
|
||
<button class="filter-btn active" data-filter="all" onclick="setFilter('all')">All</button>
|
||
<button class="filter-btn" data-filter="enabled" onclick="setFilter('enabled')">Enabled</button>
|
||
<button class="filter-btn" data-filter="disabled" onclick="setFilter('disabled')">Disabled</button>
|
||
</div>
|
||
|
||
<div class="bulk-actions">
|
||
<button class="bulk-btn" onclick="selectAllVisible()">Select All</button>
|
||
<button class="bulk-btn" onclick="enableSelected()">Enable Selected</button>
|
||
<button class="bulk-btn danger" onclick="disableSelected()">Disable Selected</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Stats Summary -->
|
||
<div class="stats-summary">
|
||
<div class="stat-item">
|
||
<span class="stat-number stat-total" id="totalPlayersCount">0</span>
|
||
<span>Total Players</span>
|
||
</div>
|
||
<div class="stat-item">
|
||
<span class="stat-number stat-enabled" id="enabledPlayersCount">0</span>
|
||
<span>Enabled</span>
|
||
</div>
|
||
<div class="stat-item">
|
||
<span class="stat-number stat-disabled" id="disabledPlayersCount">0</span>
|
||
<span>Disabled</span>
|
||
</div>
|
||
<div class="stat-item">
|
||
<span class="stat-number" id="visiblePlayersCount">0</span>
|
||
<span>Visible</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;">ID</th>
|
||
<th>Name</th>
|
||
<th class="status-col" style="width: 120px;">Status</th>
|
||
<th class="actions-col" style="width: 200px;">Actions</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="playerTableBody">
|
||
<!-- Players will be populated here -->
|
||
</tbody>
|
||
</table>
|
||
|
||
<div class="no-results" id="noResults" style="display: none;">
|
||
No players found matching your search criteria.
|
||
</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('No players selected');
|
||
return;
|
||
}
|
||
|
||
if (!confirm(`Enable ${selectedPlayers.size} selected players?`)) {
|
||
return;
|
||
}
|
||
|
||
bulkUpdatePlayers(Array.from(selectedPlayers), true);
|
||
}
|
||
|
||
function disableSelected() {
|
||
if (selectedPlayers.size === 0) {
|
||
alert('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');
|
||
const filteredPlayers = getFilteredPlayers();
|
||
|
||
tbody.innerHTML = '';
|
||
|
||
if (filteredPlayers.length === 0) {
|
||
noResults.style.display = 'block';
|
||
return;
|
||
} else {
|
||
noResults.style.display = 'none';
|
||
}
|
||
|
||
filteredPlayers.forEach(player => {
|
||
const row = document.createElement('tr');
|
||
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 ? '✓ Enabled' : '✗ Disabled'}
|
||
</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 ? 'Competing' : '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">
|
||
✏️ Edit
|
||
</button>
|
||
<button class="action-btn-small delete-btn-small"
|
||
onclick="confirmDeletePlayer(${player.id})"
|
||
title="Delete Player">
|
||
🗑️ Delete
|
||
</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.textContent = '➕ Add Player';
|
||
}
|
||
}
|
||
|
||
// Handle Enter key in add player input
|
||
function handleAddPlayerKeypress(event) {
|
||
if (event.key === 'Enter') {
|
||
event.preventDefault();
|
||
addPlayer();
|
||
}
|
||
}
|
||
|
||
// Edit player name
|
||
function editPlayer(playerId) {
|
||
const player = players.find(p => p.id === playerId);
|
||
if (!player) return;
|
||
|
||
const nameElement = document.getElementById(`playerName${playerId}`);
|
||
if (editingPlayer === playerId) {
|
||
// Save changes
|
||
const input = nameElement.querySelector('input');
|
||
if (input) {
|
||
const newName = input.value.trim();
|
||
if (newName && newName !== player.name) {
|
||
updatePlayerName(playerId, newName);
|
||
} else {
|
||
editingPlayer = null;
|
||
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
|
||
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;
|
||
renderPlayerTable();
|
||
} 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' ? '40 targets (80 shots)' :
|
||
selectedTournamentType === '4_targets' ? '4 targets (20 shots)' :
|
||
'20 targets (40 shots)';
|
||
|
||
if (!confirm(`Start league with ${enabledPlayers.length} players using ${formatText} format?`)) {
|
||
return;
|
||
}
|
||
|
||
const startBtn = document.getElementById('startLeagueBtn');
|
||
startBtn.disabled = true;
|
||
startBtn.textContent = 'Starting League...';
|
||
|
||
try {
|
||
const response = await fetch('/api/league/start', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({ tournament_type: selectedTournamentType })
|
||
});
|
||
|
||
if (response.ok) {
|
||
const result = await response.json();
|
||
alert('League started successfully!');
|
||
window.location.reload();
|
||
} else {
|
||
const error = await response.json();
|
||
alert('Failed to start league: ' + error.message);
|
||
}
|
||
} catch (error) {
|
||
console.error('Error starting league:', error);
|
||
alert('Error starting league. Please try again.');
|
||
} finally {
|
||
startBtn.disabled = false;
|
||
startBtn.textContent = '🏆 Start League (6 Tournaments)';
|
||
}
|
||
}
|
||
|
||
// Start single tournament
|
||
async function startSingleTournament() {
|
||
const enabledPlayers = players.filter(p => p.enabled);
|
||
|
||
if (enabledPlayers.length < 1) {
|
||
alert('Need at least 1 enabled player to start tournament');
|
||
return;
|
||
}
|
||
|
||
const formatText = selectedTournamentType === '40_targets' ? '40 targets (80 shots)' :
|
||
selectedTournamentType === '4_targets' ? '4 targets (20 shots)' :
|
||
'20 targets (40 shots)';
|
||
|
||
if (!confirm(`Start single tournament with ${enabledPlayers.length} players using ${formatText} format?`)) {
|
||
return;
|
||
}
|
||
|
||
const startBtn = document.getElementById('startSingleBtn');
|
||
startBtn.disabled = true;
|
||
startBtn.textContent = 'Starting...';
|
||
|
||
try {
|
||
const response = await fetch('/api/tournament/start', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({ tournament_type: selectedTournamentType })
|
||
});
|
||
|
||
if (response.ok) {
|
||
const result = await response.json();
|
||
alert('Tournament started successfully!');
|
||
window.location.reload();
|
||
} else {
|
||
const error = await response.json();
|
||
alert('Failed to start tournament: ' + error.message);
|
||
}
|
||
} catch (error) {
|
||
console.error('Error starting tournament:', error);
|
||
alert('Error starting tournament. Please try again.');
|
||
} finally {
|
||
startBtn.disabled = false;
|
||
startBtn.textContent = '🎯 Start Single Tournament';
|
||
}
|
||
}
|
||
|
||
// Start next tournament in league
|
||
async function startNextTournament() {
|
||
// Get selected joker players
|
||
const jokerCheckboxes = document.querySelectorAll('.joker-checkbox:checked:not(:disabled)');
|
||
const jokerPlayers = Array.from(jokerCheckboxes).map(cb => parseInt(cb.dataset.playerId));
|
||
|
||
if (!confirm(`Start next tournament? ${jokerPlayers.length} players will use their joker.`)) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch('/api/league/tournament/start', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({ joker_players: jokerPlayers })
|
||
});
|
||
|
||
if (response.ok) {
|
||
const result = await response.json();
|
||
alert('Tournament started successfully!');
|
||
window.location.reload();
|
||
} else {
|
||
const error = await response.json();
|
||
alert('Failed to start tournament: ' + error.message);
|
||
}
|
||
} catch (error) {
|
||
console.error('Error starting tournament:', error);
|
||
alert('Error starting tournament. Please try again.');
|
||
}
|
||
}
|
||
|
||
// Reset league
|
||
async function resetLeague() {
|
||
if (!confirm('Are you sure you want to reset the league? This will delete all league and tournament data.')) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch('/api/league/reset', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
}
|
||
});
|
||
|
||
if (response.ok) {
|
||
alert('League reset successfully!');
|
||
window.location.reload();
|
||
} else {
|
||
alert('Failed to reset league');
|
||
}
|
||
} catch (error) {
|
||
console.error('Error resetting league:', error);
|
||
alert('Error resetting league. Please try again.');
|
||
}
|
||
}
|
||
|
||
// Reset tournament
|
||
async function resetTournament() {
|
||
if (!confirm('Are you sure you want to reset the tournament? This will delete tournament data.')) {
|
||
return;
|
||
}
|
||
|
||
try {
|
||
const response = await fetch('/api/tournament/reset', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
}
|
||
});
|
||
|
||
if (response.ok) {
|
||
alert('Tournament reset successfully!');
|
||
window.location.reload();
|
||
} else {
|
||
alert('Failed to reset tournament');
|
||
}
|
||
} catch (error) {
|
||
console.error('Error resetting tournament:', error);
|
||
alert('Error resetting tournament. Please try again.');
|
||
}
|
||
}
|
||
|
||
// Modal functions
|
||
function showModal(title, message, confirmCallback) {
|
||
document.getElementById('modalTitle').textContent = title;
|
||
document.getElementById('modalMessage').textContent = message;
|
||
pendingAction = confirmCallback;
|
||
document.getElementById('confirmationModal').classList.add('active');
|
||
}
|
||
|
||
function closeModal() {
|
||
document.getElementById('confirmationModal').classList.remove('active');
|
||
pendingAction = null;
|
||
}
|
||
|
||
function confirmAction() {
|
||
if (pendingAction) {
|
||
pendingAction();
|
||
}
|
||
}
|
||
|
||
// Click outside modal to close
|
||
document.getElementById('confirmationModal').addEventListener('click', function(e) {
|
||
if (e.target === this) {
|
||
closeModal();
|
||
}
|
||
});
|
||
|
||
// Initialize page
|
||
document.addEventListener('DOMContentLoaded', function() {
|
||
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);
|
||
});
|
||
</script>
|
||
</body>
|
||
</html> |