diff --git a/__pycache__/models.cpython-312.pyc b/__pycache__/models.cpython-312.pyc index c7d22e3..f114c69 100644 Binary files a/__pycache__/models.cpython-312.pyc and b/__pycache__/models.cpython-312.pyc differ diff --git a/__pycache__/seed.cpython-312.pyc b/__pycache__/seed.cpython-312.pyc index 3de0bb1..4e4b94e 100644 Binary files a/__pycache__/seed.cpython-312.pyc and b/__pycache__/seed.cpython-312.pyc differ diff --git a/instance/team_tryouts.db b/instance/team_tryouts.db index 8721583..c56b0f3 100644 Binary files a/instance/team_tryouts.db and b/instance/team_tryouts.db differ diff --git a/routes/__pycache__/evaluations.cpython-312.pyc b/routes/__pycache__/evaluations.cpython-312.pyc index 049bfae..a1629ad 100644 Binary files a/routes/__pycache__/evaluations.cpython-312.pyc and b/routes/__pycache__/evaluations.cpython-312.pyc differ diff --git a/routes/__pycache__/main.cpython-312.pyc b/routes/__pycache__/main.cpython-312.pyc index f9019eb..efb111b 100644 Binary files a/routes/__pycache__/main.cpython-312.pyc and b/routes/__pycache__/main.cpython-312.pyc differ diff --git a/routes/__pycache__/matches.cpython-312.pyc b/routes/__pycache__/matches.cpython-312.pyc new file mode 100644 index 0000000..acfa273 Binary files /dev/null and b/routes/__pycache__/matches.cpython-312.pyc differ diff --git a/routes/__pycache__/teams.cpython-312.pyc b/routes/__pycache__/teams.cpython-312.pyc index 9fa4be0..e2017e9 100644 Binary files a/routes/__pycache__/teams.cpython-312.pyc and b/routes/__pycache__/teams.cpython-312.pyc differ diff --git a/routes/__pycache__/tryouts.cpython-312.pyc b/routes/__pycache__/tryouts.cpython-312.pyc index a757d55..a4a5ccd 100644 Binary files a/routes/__pycache__/tryouts.cpython-312.pyc and b/routes/__pycache__/tryouts.cpython-312.pyc differ diff --git a/routes/__pycache__/users.cpython-312.pyc b/routes/__pycache__/users.cpython-312.pyc index e475ff8..8236a59 100644 Binary files a/routes/__pycache__/users.cpython-312.pyc and b/routes/__pycache__/users.cpython-312.pyc differ diff --git a/routes/main.py b/routes/main.py index 10e3f86..7258134 100644 --- a/routes/main.py +++ b/routes/main.py @@ -1,8 +1,9 @@ from flask import Blueprint, render_template, redirect, url_for, flash from flask_login import login_required, current_user from extensions import db -from models import User, Tryout, Evaluation, TryoutRegistration, Team, TeamMember +from models import User, Tryout, Evaluation, TryoutRegistration, Team, TeamMember, Match, MatchParticipant from sqlalchemy import func +from datetime import datetime, date main_bp = Blueprint('main', __name__) @@ -47,6 +48,52 @@ def dashboard(): stats['average_score'] = db.session.query(func.avg(Evaluation.overall_score)).filter_by(player_id=user.id).scalar() or 0 stats['my_registrations'] = TryoutRegistration.query.filter_by(player_id=user.id).order_by(TryoutRegistration.registered_at.desc()).limit(5).all() stats['my_eval_list'] = Evaluation.query.filter_by(player_id=user.id).order_by(Evaluation.created_at.desc()).limit(5).all() + + # Get next match for each tryout the player is in + today = date.today() + next_matches = [] + for reg in stats['my_registrations']: + tryout = reg.tryout + # Find matches where player participates (team membership or direct participant) + player_teams = Team.query.join(TeamMember).filter( + Team.tryout_id == tryout.id, + TeamMember.player_id == user.id + ).all() + + # Get matches for this tryout that include the player + tryout_matches = Match.query.filter( + Match.tryout_id == tryout.id, + Match.status == 'scheduled' + ).order_by(Match.date, Match.start_time).all() + + for match in tryout_matches: + # Check if player is in this match + is_participant = False + if match.match_type == 'team_vs_team': + # Check if player is on team1 or team2 + if match.team1_id in [t.id for t in player_teams] or match.team2_id in [t.id for t in player_teams]: + is_participant = True + else: + # Check if player is in match participants + participant = MatchParticipant.query.filter_by( + match_id=match.id, + player_id=user.id + ).first() + if participant: + is_participant = True + + if is_participant: + # Check if match is upcoming + match_date = match.date + if match_date >= today: + next_matches.append({ + 'tryout': tryout, + 'match': match, + 'team': player_teams[0] if player_teams else None + }) + break # Only get the next match per tryout + + stats['next_matches'] = next_matches elif user.role == 'scout': stats['total_players'] = User.query.filter_by(role='player').count() diff --git a/routes/matches.py b/routes/matches.py index ed0f6ec..79434a2 100644 --- a/routes/matches.py +++ b/routes/matches.py @@ -73,7 +73,7 @@ def api_events(): events.append({ 'id': f'match_{match.id}', - 'title': match.title + ' (' + participants_str + ')', + 'title': match.title, 'date': match.date.strftime('%Y-%m-%d'), 'type': 'match', 'color': match_color, @@ -181,7 +181,7 @@ def api_events_for_tryout(tryout_id): events.append({ 'id': f'match_{match.id}', - 'title': match.title + ' (' + participants_str + ')', + 'title': match.title, 'date': match.date.strftime('%Y-%m-%d'), 'type': 'match', 'color': match_color, diff --git a/routes/tryouts.py b/routes/tryouts.py index 2fa670c..6fca3d3 100644 --- a/routes/tryouts.py +++ b/routes/tryouts.py @@ -172,13 +172,13 @@ def view_tryout(tryout_id): 'team2_players': [{'name': m.player.full_name, 'position': m.position} for m in match.team2.members.all()] if match.team2 else [] } elif match.match_type == 'player_vs_player': - team1_players = [p.player.full_name for p in match.participants.filter_by(team_side=1).all()] - team2_players = [p.player.full_name for p in match.participants.filter_by(team_side=2).all()] + team1_players = [{'name': p.player.full_name, 'position': p.position} for p in match.participants.filter_by(team_side=1).all() if p.player] + team2_players = [{'name': p.player.full_name, 'position': p.position} for p in match.participants.filter_by(team_side=2).all() if p.player] participants = { - 'team1': ', '.join(team1_players) if team1_players else 'TBD', - 'team2': ', '.join(team2_players) if team2_players else 'TBD', - 'team1_player_names': team1_players, - 'team2_player_names': team2_players + 'team1': 'Team 1', + 'team2': 'Team 2', + 'team1_players': team1_players, + 'team2_players': team2_players } else: participants = [p.player.full_name for p in match.participants.all()] diff --git a/static/css/style.css b/static/css/style.css index f1a371a..40ae852 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -1,28 +1,29 @@ :root { - --primary: #4f46e5; - --primary-dark: #4338ca; - --primary-light: #eef2ff; - --success: #10b981; - --success-light: #ecfdf5; - --warning: #f59e0b; - --warning-light: #fffbeb; + /* New Color Palette */ + --primary: #00984C; + --primary-dark: #003E21; + --primary-light: #E5A939; + --success: #00984C; + --success-light: #F0F2F2; + --warning: #E5A939; + --warning-light: #FEF9F0; --danger: #ef4444; --danger-light: #fef2f2; --info: #3b82f6; --info-light: #eff6ff; --secondary: #6b7280; - --secondary-light: #f3f4f6; - --dark: #1f2937; - --gray-50: #f9fafb; - --gray-100: #f3f4f6; - --gray-200: #e5e7eb; - --gray-300: #d1d5db; - --gray-400: #9ca3af; - --gray-500: #6b7280; - --gray-600: #4b5563; - --gray-700: #374151; - --gray-800: #1f2937; - --gray-900: #111827; + --secondary-light: #F0F2F2; + --dark: #12130F; + --gray-50: #F0F2F2; + --gray-100: #E8EAEB; + --gray-200: #D1D5DB; + --gray-300: #9CA3AF; + --gray-400: #6B7280; + --gray-500: #4B5563; + --gray-600: #374151; + --gray-700: #1F2937; + --gray-800: #12130F; + --gray-900: #0A0B0A; --sidebar-width: 260px; --sidebar-collapsed: 0px; --radius: 12px; @@ -33,6 +34,26 @@ --transition: all 0.2s ease; } +/* Dark Mode Variables */ +[data-theme="dark"] { + --bg-primary: #12130F; + --bg-secondary: #1A1D17; + --bg-tertiary: #232820; + --text-primary: #F0F2F2; + --text-secondary: #D1D5DB; + --text-muted: #9CA3AF; + --border-color: #2D342A; + --card-bg: #1A1D17; + --sidebar-bg: #0A0B0A; + --sidebar-text: #F0F2F2; + --sidebar-hover: rgba(240, 242, 242, 0.08); + --input-bg: #232820; + --input-border: #2D342A; + --shadow: 0 1px 3px rgba(0,0,0,0.3), 0 1px 2px rgba(0,0,0,0.2); + --shadow-md: 0 4px 6px rgba(0,0,0,0.25), 0 2px 4px rgba(0,0,0,0.2); + --shadow-lg: 0 10px 15px rgba(0,0,0,0.3), 0 4px 6px rgba(0,0,0,0.2); +} + * { margin: 0; padding: 0; box-sizing: border-box; } body { @@ -462,7 +483,7 @@ a:hover { color: var(--primary-dark); } .form-group textarea:focus { outline: none; border-color: var(--primary); - box-shadow: 0 0 0 3px rgba(79,70,229,0.1); + box-shadow: 0 0 0 3px rgba(0, 152, 76, 0.1); } .form-group textarea { resize: vertical; min-height: 80px; } @@ -533,10 +554,14 @@ a:hover { color: var(--primary-dark); } flex-direction: column; align-items: center; justify-content: center; - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + background: linear-gradient(135deg, #00984C 0%, #003E21 100%); padding: 20px; } +[data-theme="dark"] .auth-wrapper { + background: linear-gradient(135deg, #003E21 0%, #12130F 100%); +} + .auth-wrapper .flash-messages { width: 100%; max-width: 420px; @@ -606,7 +631,7 @@ a:hover { color: var(--primary-dark); } .auth-form .form-group input:focus { border-color: var(--primary); - box-shadow: 0 0 0 3px rgba(79,70,229,0.1); + box-shadow: 0 0 0 3px rgba(0, 152, 76, 0.1); outline: none; } @@ -989,6 +1014,49 @@ a:hover { color: var(--primary-dark); } color: var(--gray-600); } +/* Match Teams Display */ +.match-teams { + display: flex; + align-items: flex-start; + gap: 12px; +} + +.match-team { + flex: 1; + min-width: 0; +} + +.match-team .team-name { + font-weight: 600; + color: var(--gray-800); + font-size: 0.9rem; + margin-bottom: 4px; + display: block; +} + +.match-vs { + font-weight: 700; + color: var(--primary); + font-size: 0.85rem; + padding: 0 4px; + margin-top: 2px; +} + +.team-players-list { + list-style: none; + padding: 0; + margin: 0; + font-size: 0.85rem; +} + +.team-players-list li { + padding: 2px 0; + color: var(--gray-600); + display: flex; + align-items: center; + gap: 6px; +} + @media (max-width: 768px) { .sidebar { width: var(--sidebar-collapsed); @@ -1161,3 +1229,273 @@ a:hover { color: var(--primary-dark); } font-size: 0.85rem; font-weight: 500; } + +/* Dark Mode Styles */ +[data-theme="dark"] body { + background: var(--bg-primary); + color: var(--text-primary); +} + +[data-theme="dark"] .sidebar { + background: var(--sidebar-bg); + border-right: 1px solid var(--border-color); +} + +[data-theme="dark"] .sidebar-header { + border-bottom: 1px solid var(--border-color); +} + +[data-theme="dark"] .user-badge { + background: rgba(240, 242, 242, 0.05); +} + +[data-theme="dark"] .nav-links li a { + color: var(--text-muted); +} + +[data-theme="dark"] .nav-links li a:hover, +[data-theme="dark"] .nav-links li a.active { + background: var(--sidebar-hover); + color: var(--sidebar-text); +} + +[data-theme="dark"] .nav-divider { + background: var(--border-color); +} + +[data-theme="dark"] .top-bar { + background: var(--bg-secondary); + border-bottom: 1px solid var(--border-color); +} + +[data-theme="dark"] .page-header h1 { + color: var(--text-primary); +} + +[data-theme="dark"] .content { + background: var(--bg-primary); +} + +[data-theme="dark"] .card { + background: var(--card-bg); + box-shadow: var(--shadow); +} + +[data-theme="dark"] .card-header { + border-bottom: 1px solid var(--border-color); +} + +[data-theme="dark"] .stat-card, +[data-theme="dark"] .team-card { + background: var(--card-bg); +} + +[data-theme="dark"] .stat-info h3, +[data-theme="dark"] .team-name, +[data-theme="dark"] .detail-value { + color: var(--text-primary); +} + +[data-theme="dark"] .stat-info p, +[data-theme="dark"] .detail-label, +[data-theme="dark"] .team-members li, +[data-theme="dark"] .team-members-list li, +[data-theme="dark"] .team-roster-display, +[data-theme="dark"] .form-text, +[data-theme="dark"] .time-selection-hint, +[data-theme="dark"] .randomize-preview { + color: var(--text-secondary); +} + +[data-theme="dark"] .stat-item { + background: var(--bg-tertiary); +} + +[data-theme="dark"] .table th { + color: var(--text-secondary); + border-bottom: 2px solid var(--border-color); +} + +[data-theme="dark"] .table td { + border-bottom: 1px solid var(--border-color); +} + +[data-theme="dark"] .table tbody tr:hover { + background: var(--bg-tertiary); +} + +[data-theme="dark"] .form-group label { + color: var(--text-secondary); +} + +[data-theme="dark"] .form-group input, +[data-theme="dark"] .form-group select, +[data-theme="dark"] .form-group textarea, +[data-theme="dark"] .form-select, +[data-theme="dark"] .form-input, +[data-theme="dark"] .randomize-input { + background: var(--input-bg); + border-color: var(--input-border); + color: var(--text-primary); +} + +[data-theme="dark"] .form-group input::placeholder, +[data-theme="dark"] .form-group textarea::placeholder { + color: var(--text-muted); +} + +[data-theme="dark"] .form-group input:focus, +[data-theme="dark"] .form-group select:focus, +[data-theme="dark"] .form-group textarea:focus { + box-shadow: 0 0 0 3px rgba(0, 152, 76, 0.2); +} + +[data-theme="dark"] .form-actions { + border-top: 1px solid var(--border-color); +} + +[data-theme="dark"] .modal-content { + background: var(--card-bg); +} + +[data-theme="dark"] .modal-header { + border-bottom: 1px solid var(--border-color); +} + +[data-theme="dark"] .modal-close { + color: var(--text-muted); +} + +[data-theme="dark"] .modal-close:hover { + color: var(--text-primary); +} + +[data-theme="dark"] .checkbox-label { + background: var(--bg-tertiary); + border-color: var(--border-color); + color: var(--text-secondary); +} + +[data-theme="dark"] .checkbox-label:hover { + background: var(--bg-secondary); +} + +[data-theme="dark"] .section-divider { + border-top: 1px solid var(--border-color); +} + +[data-theme="dark"] .section-title, +[data-theme="dark"] .empty-state h3 { + color: var(--text-primary); +} + +[data-theme="dark"] .empty-state i, +[data-theme="dark"] .empty-state p { + color: var(--text-muted); +} + +[data-theme="dark"] .auth-container { + background: var(--card-bg); +} + +[data-theme="dark"] .auth-header h2 { + color: var(--text-primary); +} + +[data-theme="dark"] .auth-header p, +[data-theme="dark"] .auth-link { + color: var(--text-secondary); +} + +[data-theme="dark"] .auth-form .form-group label { + color: var(--text-secondary); +} + +[data-theme="dark"] .auth-form .form-group label i { + color: var(--text-muted); +} + +[data-theme="dark"] .auth-form .form-group input { + background: var(--input-bg); + border-color: var(--input-border); + color: var(--text-primary); +} + +[data-theme="dark"] .auth-form .form-group input:focus { + box-shadow: 0 0 0 3px rgba(0, 152, 76, 0.2); +} + +[data-theme="dark"] .position-tag { + background: var(--bg-tertiary); + color: var(--primary); +} + +[data-theme="dark"] .match-team .team-name { + color: var(--text-primary); +} + +[data-theme="dark"] .merged-disponibility-time-block { + background: var(--bg-tertiary); + border-color: var(--border-color); + color: var(--text-secondary); +} + +[data-theme="dark"] .merged-disponibility-time-block:hover { + background: var(--bg-secondary); +} + +[data-theme="dark"] .merged-disponibility-selected-display { + background: var(--bg-tertiary); +} + +[data-theme="dark"] .randomize-section { + background: var(--bg-tertiary); +} + +[data-theme="dark"] .btn-secondary { + background: var(--bg-tertiary); + color: var(--text-secondary); +} + +[data-theme="dark"] .btn-secondary:hover { + background: var(--bg-secondary); +} + +[data-theme="dark"] .btn-outline { + background: transparent; + border-color: var(--border-color); + color: var(--text-secondary); +} + +[data-theme="dark"] .btn-outline:hover { + background: var(--bg-tertiary); +} + +/* Dark Mode Toggle Button */ +.dark-mode-toggle { + background: none; + border: none; + font-size: 1.1rem; + color: var(--gray-500); + cursor: pointer; + padding: 6px 10px; + border-radius: var(--radius-sm); + display: flex; + align-items: center; + gap: 6px; + transition: var(--transition); +} + +.dark-mode-toggle:hover { + background: var(--gray-100); + color: var(--gray-700); +} + +[data-theme="dark"] .dark-mode-toggle { + color: var(--text-secondary); +} + +[data-theme="dark"] .dark-mode-toggle:hover { + background: var(--bg-tertiary); + color: var(--text-primary); +} diff --git a/static/js/main.js b/static/js/main.js index 1cb3d53..bfb9890 100644 --- a/static/js/main.js +++ b/static/js/main.js @@ -1,3 +1,33 @@ +// Dark Mode Toggle +function toggleDarkMode() { + const body = document.documentElement; + const isDark = body.getAttribute('data-theme') === 'dark'; + const toggle = document.getElementById('darkModeToggle'); + + if (isDark) { + body.removeAttribute('data-theme'); + localStorage.setItem('theme', 'light'); + toggle.innerHTML = ''; + } else { + body.setAttribute('data-theme', 'dark'); + localStorage.setItem('theme', 'dark'); + toggle.innerHTML = ''; + } +} + +// Load saved theme preference +function loadTheme() { + const savedTheme = localStorage.getItem('theme'); + const toggle = document.getElementById('darkModeToggle'); + + if (savedTheme === 'dark') { + document.documentElement.setAttribute('data-theme', 'dark'); + if (toggle) { + toggle.innerHTML = ''; + } + } +} + // Sidebar toggle for mobile function toggleSidebar() { const sidebar = document.getElementById('sidebar'); @@ -17,6 +47,9 @@ document.addEventListener('click', function(event) { // Auto-dismiss flash messages document.addEventListener('DOMContentLoaded', function() { + // Load theme preference + loadTheme(); + const alerts = document.querySelectorAll('.alert-dismissible'); alerts.forEach(function(alert) { setTimeout(function() { @@ -40,4 +73,4 @@ document.querySelectorAll('[data-confirm]').forEach(function(el) { e.preventDefault(); } }); -}); \ No newline at end of file +}); diff --git a/templates/layouts/base.html b/templates/layouts/base.html index 136598f..1e2a8cb 100644 --- a/templates/layouts/base.html +++ b/templates/layouts/base.html @@ -84,7 +84,7 @@