diff --git a/__pycache__/extensions.cpython-313.pyc b/__pycache__/extensions.cpython-313.pyc index a36f59a..4124b08 100644 Binary files a/__pycache__/extensions.cpython-313.pyc and b/__pycache__/extensions.cpython-313.pyc differ diff --git a/__pycache__/models.cpython-313.pyc b/__pycache__/models.cpython-313.pyc index a023f1b..4fae20e 100644 Binary files a/__pycache__/models.cpython-313.pyc and b/__pycache__/models.cpython-313.pyc differ diff --git a/__pycache__/seed.cpython-313.pyc b/__pycache__/seed.cpython-313.pyc index ffb56f6..cbc4531 100644 Binary files a/__pycache__/seed.cpython-313.pyc and b/__pycache__/seed.cpython-313.pyc differ diff --git a/instance/team_tryouts.db b/instance/team_tryouts.db index 0d34403..0fa567a 100644 Binary files a/instance/team_tryouts.db and b/instance/team_tryouts.db differ diff --git a/models.py b/models.py index eedd70e..d2863de 100644 --- a/models.py +++ b/models.py @@ -65,7 +65,6 @@ class User(UserMixin, db.Model): full_name: User's display name. email: User's email address. phone: Optional phone number. - team_id: Foreign key to the user's organization team. is_active_account: Whether the account is active. games: Comma-separated list of games the user plays. discord_username: User's Discord handle. @@ -79,7 +78,6 @@ class User(UserMixin, db.Model): full_name = db.Column(db.String(100), nullable=False) email = db.Column(db.String(120), unique=True, nullable=False) phone = db.Column(db.String(20), nullable=True) - team_id = db.Column(db.Integer, db.ForeignKey('org_teams.id'), nullable=True) is_active_account = db.Column(db.Boolean, default=True) created_at = db.Column(db.DateTime, default=datetime.utcnow) @@ -205,6 +203,14 @@ class User(UserMixin, db.Model): """ return {gt.game: {'gamertag': gt.gamertag, 'platform': gt.platform} for gt in self.gamertags} + def get_org_teams(self): + """Return all organization teams this player belongs to. + + Returns: + list: List of OrgTeam objects the player is assigned to. + """ + return [tp.org_team for tp in self.team_placements] + # Platform options for games that require platform specification GAME_PLATFORMS = { @@ -296,6 +302,35 @@ class UserGamertag(db.Model): return url +class TeamPlayer(db.Model): + """Many-to-many relationship between players and organization teams. + + Allows a player to be in multiple teams with a status of starter or substitute. + + Attributes: + id: Unique identifier. + player_id: Foreign key to the player. + org_team_id: Foreign key to the organization team. + status: Player status on the team ('starter' or 'substitute'). + position: Player's position on the team (optional). + added_at: Timestamp when player was added. + """ + __tablename__ = 'team_players' + id = db.Column(db.Integer, primary_key=True) + player_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) + org_team_id = db.Column(db.Integer, db.ForeignKey('org_teams.id'), nullable=False) + status = db.Column(db.String(20), nullable=False, default='starter') # 'starter' or 'substitute' + position = db.Column(db.String(50), nullable=True) + added_at = db.Column(db.DateTime, default=datetime.utcnow) + + player = db.relationship('User', foreign_keys=[player_id], backref='team_placements') + org_team = db.relationship('OrgTeam', foreign_keys=[org_team_id], backref='team_players') + + __table_args__ = ( + db.UniqueConstraint('player_id', 'org_team_id', name='unique_player_org_team'), + ) + + class OrgTeam(db.Model): """Persistent organization teams (e.g., Varsity, JV) that exist across tryouts. @@ -306,6 +341,7 @@ class OrgTeam(db.Model): id: Unique identifier. name: Team name (e.g., "Varsity", "Junior Varsity"). coach_id: Foreign key to the assigned coach. + manager_id: Foreign key to the assigned manager. created_by: Foreign key to the user who created the team. created_at: Timestamp of team creation. """ @@ -313,12 +349,30 @@ class OrgTeam(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(100), nullable=False, unique=True) coach_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True) + manager_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=True) created_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False) created_at = db.Column(db.DateTime, default=datetime.utcnow) coach = db.relationship('User', foreign_keys=[coach_id], backref='coached_org_team', uselist=False) + manager = db.relationship('User', foreign_keys=[manager_id], backref='managed_org_team', uselist=False) creator = db.relationship('User', foreign_keys=[created_by]) - players = db.relationship('User', foreign_keys='User.team_id', backref='org_team', lazy='dynamic') + + @property + def players(self): + """Get all players assigned to this team. + + Returns: + list: List of User objects assigned to this team. + """ + return [tp.player for tp in self.team_players] + + def get_players_with_status(self): + """Get all players with their status on this team. + + Returns: + list: List of dicts with 'player' and 'status' keys. + """ + return [{'player': tp.player, 'status': tp.status, 'position': tp.position} for tp in self.team_players] class Tryout(db.Model): @@ -645,7 +699,7 @@ class Contract(db.Model): # Manager can view contracts for players on their teams if user.role == 'manager': player = User.query.get(self.player_id) - if player and player.team_id: + if player and player.get_org_teams(): return True # Coach can view contracts for players on their team if user.role == 'coach': diff --git a/routes/__pycache__/auth.cpython-313.pyc b/routes/__pycache__/auth.cpython-313.pyc index b2adccc..7240504 100644 Binary files a/routes/__pycache__/auth.cpython-313.pyc and b/routes/__pycache__/auth.cpython-313.pyc differ diff --git a/routes/__pycache__/evaluations.cpython-313.pyc b/routes/__pycache__/evaluations.cpython-313.pyc index 9eeb5a6..59a7236 100644 Binary files a/routes/__pycache__/evaluations.cpython-313.pyc and b/routes/__pycache__/evaluations.cpython-313.pyc differ diff --git a/routes/__pycache__/main.cpython-313.pyc b/routes/__pycache__/main.cpython-313.pyc index 2e711ad..a0e4776 100644 Binary files a/routes/__pycache__/main.cpython-313.pyc and b/routes/__pycache__/main.cpython-313.pyc differ diff --git a/routes/__pycache__/matches.cpython-313.pyc b/routes/__pycache__/matches.cpython-313.pyc index 9b144d3..a874955 100644 Binary files a/routes/__pycache__/matches.cpython-313.pyc and b/routes/__pycache__/matches.cpython-313.pyc differ diff --git a/routes/__pycache__/teams.cpython-313.pyc b/routes/__pycache__/teams.cpython-313.pyc index ce9242a..e06a112 100644 Binary files a/routes/__pycache__/teams.cpython-313.pyc and b/routes/__pycache__/teams.cpython-313.pyc differ diff --git a/routes/__pycache__/tryouts.cpython-313.pyc b/routes/__pycache__/tryouts.cpython-313.pyc index 399e6fb..af03231 100644 Binary files a/routes/__pycache__/tryouts.cpython-313.pyc and b/routes/__pycache__/tryouts.cpython-313.pyc differ diff --git a/routes/__pycache__/users.cpython-313.pyc b/routes/__pycache__/users.cpython-313.pyc index 1ddb4ab..0f1cb05 100644 Binary files a/routes/__pycache__/users.cpython-313.pyc and b/routes/__pycache__/users.cpython-313.pyc differ diff --git a/routes/evaluations.py b/routes/evaluations.py index 817df38..d11ed3a 100644 --- a/routes/evaluations.py +++ b/routes/evaluations.py @@ -6,7 +6,7 @@ This module handles player evaluation creation, management, and viewing. from flask import Blueprint, render_template, redirect, url_for, flash, request from flask_login import login_required, current_user from extensions import db -from models import User, Tryout, Evaluation, TryoutRegistration +from models import User, Tryout, Evaluation, TryoutRegistration, GAME_POSITIONS from sqlalchemy import func evaluations_bp = Blueprint('evaluations', __name__, url_prefix='/evaluations') @@ -178,7 +178,8 @@ def evaluate_player(tryout_id, player_id): tryout=tryout, player=player, existing_eval=existing_eval, - evaluators=evaluators) + evaluators=evaluators, + game_positions=GAME_POSITIONS) @evaluations_bp.route('//players') diff --git a/routes/teams.py b/routes/teams.py index 6b94204..ebd1ea5 100644 --- a/routes/teams.py +++ b/routes/teams.py @@ -3,10 +3,10 @@ This module handles CRUD operations for organization teams and player assignments. """ -from flask import Blueprint, render_template, redirect, url_for, flash, request +from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify from flask_login import login_required, current_user from extensions import db -from models import OrgTeam, User, Team, TeamMember, PersonalNote, TeamNote, Tryout +from models import OrgTeam, User, Team, TeamMember, PersonalNote, TeamNote, Tryout, TeamPlayer teams_bp = Blueprint('teams', __name__, url_prefix='/teams') @@ -35,8 +35,9 @@ def list_teams(): return redirect(url_for('main.dashboard')) coaches = User.query.filter_by(role='coach').order_by(User.full_name).all() + managers = User.query.filter_by(role='manager').order_by(User.full_name).all() all_players = User.query.filter_by(role='player').order_by(User.full_name).all() - return render_template('pages/teams.html', teams=teams, coaches=coaches, all_players=all_players, can_manage=can_manage) + return render_template('pages/teams.html', teams=teams, coaches=coaches, managers=managers, all_players=all_players, can_manage=can_manage) @teams_bp.route('/create', methods=['POST']) @@ -47,6 +48,7 @@ def create_team(): Args: name: Team name from form. coach_id: Optional coach assignment from form. + manager_id: Optional manager assignment from form. Returns: Response: Redirect to teams list with status message. @@ -57,6 +59,7 @@ def create_team(): name = request.form.get('name') coach_id = request.form.get('coach_id') + manager_id = request.form.get('manager_id') if not name: flash('Team name is required.', 'danger') @@ -70,6 +73,7 @@ def create_team(): team = OrgTeam( name=name, coach_id=int(coach_id) if coach_id else None, + manager_id=int(manager_id) if manager_id else None, created_by=current_user.id ) db.session.add(team) @@ -87,6 +91,7 @@ def edit_team(team_id): team_id: The ID of the team to edit. name: New team name from form. coach_id: New coach assignment from form. + manager_id: New manager assignment from form. Returns: Response: Redirect to teams list with status message. @@ -98,6 +103,7 @@ def edit_team(team_id): name = request.form.get('name') coach_id = request.form.get('coach_id') + manager_id = request.form.get('manager_id') if not name: flash('Team name is required.', 'danger') @@ -110,6 +116,7 @@ def edit_team(team_id): team.name = name team.coach_id = int(coach_id) if coach_id else None + team.manager_id = int(manager_id) if manager_id else None db.session.commit() flash(f'Team "{name}" updated successfully!', 'success') return redirect(url_for('teams.list_teams')) @@ -144,22 +151,71 @@ def delete_team(team_id): t.target_org_team_id = None db.session.commit() + # Remove all team_players associations + TeamPlayer.query.filter_by(org_team_id=team_id).delete() + db.session.commit() + db.session.delete(team) db.session.commit() flash(f'Team "{name}" deleted successfully.', 'success') return redirect(url_for('teams.list_teams')) +@teams_bp.route('//remove_coach', methods=['POST']) +@login_required +def remove_coach(team_id): + """Remove the coach from an organization team. + + Args: + team_id: The ID of the team. + + Returns: + Response: Redirect to teams list with status message. + """ + team = OrgTeam.query.get_or_404(team_id) + if not current_user.can_manage_this_org_team(team): + flash('Permission denied.', 'danger') + return redirect(url_for('teams.list_teams')) + + team.coach_id = None + db.session.commit() + flash(f'Coach removed from {team.name}.', 'success') + return redirect(url_for('teams.list_teams')) + + +@teams_bp.route('//remove_manager', methods=['POST']) +@login_required +def remove_manager(team_id): + """Remove the manager from an organization team. + + Args: + team_id: The ID of the team. + + Returns: + Response: Redirect to teams list with status message. + """ + team = OrgTeam.query.get_or_404(team_id) + if not current_user.can_manage_this_org_team(team): + flash('Permission denied.', 'danger') + return redirect(url_for('teams.list_teams')) + + team.manager_id = None + db.session.commit() + flash(f'Manager removed from {team.name}.', 'success') + return redirect(url_for('teams.list_teams')) + + @teams_bp.route('//add_player', methods=['POST']) @login_required def add_player(team_id): """Add a player to an organization team. - If player is already on another team, they will be moved. + Players can now be in multiple teams. Args: team_id: The ID of the team to add the player to. player_id: The ID of the player to add. + status: The player's status (starter or substitute). Returns: Response: Redirect to teams list with status message. @@ -169,7 +225,7 @@ def add_player(team_id): flash('Permission denied.', 'danger') return redirect(url_for('teams.list_teams')) player_id = request.form.get('player_id') - position = request.form.get('position', '') + status = request.form.get('status', 'starter') if not player_id: flash('Please select a player.', 'danger') @@ -180,15 +236,19 @@ def add_player(team_id): flash('Can only assign players to teams.', 'danger') return redirect(url_for('teams.list_teams')) - # If player already belongs to a different team, remove them first - if player.team_id and player.team_id != team_id: - old_team = OrgTeam.query.get(player.team_id) - flash(f'{player.full_name} was moved from {old_team.name} to {team.name}.', 'info') - elif player.team_id == team_id: + # Check if player is already on this team + existing = TeamPlayer.query.filter_by(player_id=player.id, org_team_id=team.id).first() + if existing: flash(f'{player.full_name} is already on {team.name}.', 'info') return redirect(url_for('teams.list_teams')) - player.team_id = team_id + # Add player to the team using TeamPlayer model (allows multiple teams) + tp = TeamPlayer( + player_id=player.id, + org_team_id=team.id, + status=status + ) + db.session.add(tp) db.session.commit() flash(f'{player.full_name} added to {team.name}!', 'success') return redirect(url_for('teams.list_teams')) @@ -213,16 +273,49 @@ def remove_player(team_id, player_id): player = User.query.get_or_404(player_id) - if player.team_id != team_id: + tp = TeamPlayer.query.filter_by(player_id=player_id, org_team_id=team_id).first() + if not tp: flash(f'{player.full_name} is not on {team.name}.', 'danger') return redirect(url_for('teams.list_teams')) - player.team_id = None + db.session.delete(tp) db.session.commit() flash(f'{player.full_name} removed from {team.name}.', 'success') return redirect(url_for('teams.list_teams')) +@teams_bp.route('//toggle_status/', methods=['POST']) +@login_required +def toggle_player_status(team_id, player_id): + """Toggle a player's status between starter and substitute. + + Args: + team_id: The ID of the team. + player_id: The ID of the player. + + Returns: + Response: JSON with new status. + """ + team = OrgTeam.query.get_or_404(team_id) + if not current_user.can_manage_this_org_team(team): + return jsonify({'error': 'Permission denied'}), 403 + + tp = TeamPlayer.query.filter_by(player_id=player_id, org_team_id=team_id).first() + if not tp: + return jsonify({'error': 'Player not found on this team'}), 404 + + # Toggle status + tp.status = 'substitute' if tp.status == 'starter' else 'starter' + db.session.commit() + + return jsonify({ + 'success': True, + 'player_id': player_id, + 'new_status': tp.status, + 'player_name': tp.player.full_name + }) + + # Note actions from team page @@ -284,7 +377,8 @@ def add_player_note(team_id, player_id): return redirect(url_for('teams.list_teams')) # Verify player belongs to this team - if player.team_id != team_id: + tp = TeamPlayer.query.filter_by(player_id=player_id, org_team_id=team_id).first() + if not tp: flash(f'{player.full_name} is not on {team.name}.', 'danger') return redirect(url_for('teams.list_teams')) @@ -300,4 +394,4 @@ def add_player_note(team_id, player_id): db.session.commit() flash(f'Note added for {player.full_name}!', 'success') - return redirect(url_for('teams.list_teams')) + return redirect(url_for('teams.list_teams')) \ No newline at end of file diff --git a/routes/users.py b/routes/users.py index 511f6de..94875b2 100644 --- a/routes/users.py +++ b/routes/users.py @@ -8,7 +8,7 @@ import os from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify, send_file from flask_login import login_required, current_user from extensions import db, hash_password -from models import User, ROLES, ESPORT_GAMES, PlayerDisponibility, UserGamertag, GAME_PLATFORMS, Contract, OrgTeam, CoachAvailability, TeamNote, PersonalNote, OneOnOneRequest, Match, Team, TeamMember, MatchParticipant, Tryout, TryoutRegistration +from models import User, ROLES, ESPORT_GAMES, PlayerDisponibility, UserGamertag, GAME_PLATFORMS, Contract, OrgTeam, CoachAvailability, TeamNote, PersonalNote, OneOnOneRequest, Match, Team, TeamMember, MatchParticipant, Tryout, TryoutRegistration, TeamPlayer from werkzeug.utils import secure_filename from datetime import datetime, timedelta, date as date_type import requests @@ -514,8 +514,8 @@ def can_manage_player_contract(user, player_id): if user.role == 'coach': org_team = OrgTeam.query.filter_by(coach_id=user.id).first() if org_team: - player = User.query.get(player_id) - if player and player.team_id == org_team.id: + tp = TeamPlayer.query.filter_by(player_id=player_id, org_team_id=org_team.id).first() + if tp: return True return False @@ -542,7 +542,8 @@ def list_contracts(): if current_user.role == 'coach': org_team = OrgTeam.query.filter_by(coach_id=current_user.id).first() if org_team: - players = User.query.filter_by(role='player', team_id=org_team.id).all() + player_ids = [tp.player_id for tp in TeamPlayer.query.filter_by(org_team_id=org_team.id).all()] + players = User.query.filter(User.id.in_(player_ids)).all() if player_ids else [] else: # president or manager players = User.query.filter_by(role='player').all() @@ -571,7 +572,11 @@ def upload_contract(): # Get players this user can manage if current_user.role == 'coach': org_team = OrgTeam.query.filter_by(coach_id=current_user.id).first() - players = User.query.filter_by(role='player', team_id=org_team.id).all() if org_team else [] + if org_team: + player_ids = [tp.player_id for tp in TeamPlayer.query.filter_by(org_team_id=org_team.id).all()] + players = User.query.filter(User.id.in_(player_ids)).all() if player_ids else [] + else: + players = [] else: players = User.query.filter_by(role='player').all() @@ -598,7 +603,8 @@ def upload_contract(): # Get player info for folder structure player = User.query.get_or_404(player_id) - team = OrgTeam.query.get(player.team_id) if player.team_id else None + player_teams = player.get_org_teams() + team = player_teams[0] if player_teams else None # Create team folder if team exists if team: @@ -621,7 +627,7 @@ def upload_contract(): # Create database record contract = Contract( player_id=player_id, - team_id=player.team_id, + team_id=team.id if team else None, uploaded_by_id=current_user.id, original_filename=original_filename, stored_filename=stored_filename, @@ -822,8 +828,9 @@ def one_on_one(): flash('Only players can request One on One sessions.', 'danger') return redirect(url_for('main.dashboard')) - # Get player's team and coach - org_team = OrgTeam.query.get(current_user.team_id) if current_user.team_id else None + # Get player's teams and coach (use the first team the player is on) + org_teams = current_user.get_org_teams() + org_team = org_teams[0] if org_teams else None coach = User.query.get(org_team.coach_id) if org_team and org_team.coach_id else None if not coach: @@ -1062,7 +1069,8 @@ def api_get_coach_availability(coach_id): return jsonify({'error': 'Unauthorized'}), 403 # Players can only view their own coach's availability - org_team = OrgTeam.query.get(current_user.team_id) if current_user.team_id else None + player_teams = current_user.get_org_teams() + org_team = player_teams[0] if player_teams else None if org_team and org_team.coach_id != coach_id: return jsonify({'error': 'Unauthorized'}), 403 @@ -1144,7 +1152,8 @@ def manage_personal_notes(): org_team = OrgTeam.query.filter_by(coach_id=current_user.id).first() players = [] if org_team: - players = User.query.filter_by(role='player', team_id=org_team.id).order_by(User.full_name).all() + player_ids = [tp.player_id for tp in TeamPlayer.query.filter_by(org_team_id=org_team.id).all()] + players = User.query.filter(User.id.in_(player_ids)).order_by(User.full_name).all() if player_ids else [] if request.method == 'POST': player_id = request.form.get('player_id', type=int) @@ -1200,7 +1209,8 @@ def notes_dashboard(): org_team = OrgTeam.query.filter_by(coach_id=current_user.id).first() players = [] if org_team: - players = User.query.filter_by(role='player', team_id=org_team.id).order_by(User.full_name).all() + player_ids = [tp.player_id for tp in TeamPlayer.query.filter_by(org_team_id=org_team.id).all()] + players = User.query.filter(User.id.in_(player_ids)).order_by(User.full_name).all() if player_ids else [] # Get existing team notes team_notes = [] @@ -1257,8 +1267,9 @@ def my_notes(): flash('Only players can view their notes.', 'danger') return redirect(url_for('main.dashboard')) - # Get player's team and coach - org_team = OrgTeam.query.get(current_user.team_id) if current_user.team_id else None + # Get player's teams and coach (use the first team the player is on) + player_teams = current_user.get_org_teams() + org_team = player_teams[0] if player_teams else None # Get all personal notes for this player with eager loading for relationships personal_notes = PersonalNote.query.filter_by(player_id=current_user.id).order_by(PersonalNote.created_at.desc()).all() @@ -1295,7 +1306,8 @@ def add_personal_note(): if current_user.role == 'coach': org_team = OrgTeam.query.filter_by(coach_id=current_user.id).first() if org_team: - players = User.query.filter_by(role='player', team_id=org_team.id).order_by(User.full_name).all() + player_ids = [tp.player_id for tp in TeamPlayer.query.filter_by(org_team_id=org_team.id).all()] + players = User.query.filter(User.id.in_(player_ids)).order_by(User.full_name).all() if player_ids else [] elif current_user.role in ['president', 'manager']: players = User.query.filter_by(role='player').order_by(User.full_name).all() @@ -1484,7 +1496,8 @@ def add_note_from_tryout(tryout_id): # If coach, filter to only their team players; otherwise include all if org_team: - players = User.query.filter(User.id.in_(player_ids), User.team_id == org_team.id).order_by(User.full_name).all() + team_player_ids = [tp.player_id for tp in TeamPlayer.query.filter_by(org_team_id=org_team.id).all()] + players = User.query.filter(User.id.in_(player_ids), User.id.in_(team_player_ids)).order_by(User.full_name).all() else: players = User.query.filter(User.id.in_(player_ids)).order_by(User.full_name).all() diff --git a/seed.py b/seed.py index 18bab11..f5d1604 100644 --- a/seed.py +++ b/seed.py @@ -6,7 +6,7 @@ users, tryouts, teams, evaluations, and player disponibilities. from sqlalchemy import text from extensions import db, hash_password -from models import User, Tryout, TryoutRegistration, Evaluation, Team, TeamMember, OrgTeam, PlayerDisponibility, UserGamertag, CoachAvailability, TeamNote, PersonalNote, Match, MatchParticipant +from models import User, Tryout, TryoutRegistration, Evaluation, Team, TeamMember, OrgTeam, PlayerDisponibility, UserGamertag, CoachAvailability, TeamNote, PersonalNote, Match, MatchParticipant, TeamPlayer from datetime import datetime, timedelta, time import random @@ -30,6 +30,7 @@ def seed_database(): db.session.execute(text('DELETE FROM player_disponibilities')) db.session.execute(text('DELETE FROM match_participants')) db.session.execute(text('DELETE FROM matches')) + db.session.execute(text('DELETE FROM team_players')) db.session.execute(text('DELETE FROM team_members')) db.session.execute(text('DELETE FROM teams')) db.session.execute(text('DELETE FROM evaluations')) @@ -323,19 +324,20 @@ def seed_database(): db.session.commit() print("[OK] Created 2 tryout-specific teams with player assignments") - # Assign players to organization teams + # Assign players to organization teams (using TeamPlayer for many-to-many) org_team_assignments = [ - (org_teams[0], players[0]), - (org_teams[0], players[1]), - (org_teams[0], players[2]), - (org_teams[1], players[3]), - (org_teams[1], players[4]), - (org_teams[1], players[5]), - (org_teams[2], players[6]), - (org_teams[2], players[7]), + (org_teams[0], players[0], 'starter'), + (org_teams[0], players[1], 'starter'), + (org_teams[0], players[2], 'substitute'), + (org_teams[1], players[3], 'starter'), + (org_teams[1], players[4], 'starter'), + (org_teams[1], players[5], 'substitute'), + (org_teams[2], players[6], 'starter'), + (org_teams[2], players[7], 'substitute'), ] - for org_team, player in org_team_assignments: - player.team_id = org_team.id + for org_team, player, status in org_team_assignments: + tp = TeamPlayer(player_id=player.id, org_team_id=org_team.id, status=status) + db.session.add(tp) db.session.commit() # Create sample disponibilities for players diff --git a/templates/pages/evaluate_player.html b/templates/pages/evaluate_player.html index 19069e1..da07645 100644 --- a/templates/pages/evaluate_player.html +++ b/templates/pages/evaluate_player.html @@ -100,7 +100,7 @@ - {% set positions = GAME_POSITIONS.get(tryout.game, []) %} + {% set positions = game_positions.get(tryout.game, []) %}
diff --git a/templates/pages/teams.html b/templates/pages/teams.html index 7bb67c7..780a5e0 100644 --- a/templates/pages/teams.html +++ b/templates/pages/teams.html @@ -21,11 +21,11 @@
-
+
-
+
+
+ + +
@@ -50,13 +59,36 @@

{{ team.name }}

- {% if team.coach %} - Coach: {{ team.coach.full_name }} - {% else %} - No coach assigned - {% endif %} +
+ {% if team.coach %} + Coach: {{ team.coach.full_name }} + {% if can_manage %} + + + + + {% endif %} + {% else %} + No coach assigned + {% endif %} + {% if team.manager %} + Manager: {{ team.manager.full_name }} + {% if can_manage %} +
+ + +
+ {% endif %} + {% else %} + No manager assigned + {% endif %} +
{% if can_manage %} -
@@ -79,6 +111,7 @@ Player + Status Position / Role Email Phone @@ -88,20 +121,34 @@ - {% for player in team.players %} + {% for entry in team.get_players_with_status() %}
-
{{ player.full_name[:2] | upper }}
- {{ player.full_name }} +
{{ entry.player.full_name[:2] | upper }}
+ {{ entry.player.full_name }}
- {{ player.role | capitalize }} - {{ player.email }} - {{ player.phone or '-' }} + + {% if can_manage_team %} + + {% else %} + + {{ entry.status | capitalize }} + + {% endif %} + + {{ entry.player.role | capitalize }} + {{ entry.player.email }} + {{ entry.player.phone or '-' }} {% if can_manage_team %} - + @@ -190,6 +241,15 @@ {% endfor %}
+
+ + +
@@ -208,24 +268,56 @@ function hideCreateForm() { document.getElementById('createTeamForm').classList.add('hidden'); } +function toggleStatus(btn) { + var teamId = btn.getAttribute('data-team-id'); + var playerId = btn.getAttribute('data-player-id'); + + fetch('/teams/' + teamId + '/toggle_status/' + playerId, { + method: 'POST', + headers: { + 'X-CSRFToken': '{{ csrf_token() }}', + 'Content-Type': 'application/json' + } + }) + .then(function(response) { return response.json(); }) + .then(function(data) { + if (data.success) { + btn.textContent = data.new_status.charAt(0).toUpperCase() + data.new_status.slice(1); + if (data.new_status === 'starter') { + btn.classList.remove('btn-warning'); + btn.classList.add('btn-success'); + } else { + btn.classList.remove('btn-success'); + btn.classList.add('btn-warning'); + } + } + }) + .catch(function(error) { + console.error('Error:', error); + }); +} + document.addEventListener('DOMContentLoaded', function() { document.querySelectorAll('.edit-team-btn').forEach(function(btn) { btn.addEventListener('click', function() { var teamId = this.getAttribute('data-team-id'); var teamName = this.getAttribute('data-team-name'); var coachId = this.getAttribute('data-coach-id'); + var managerId = this.getAttribute('data-manager-id'); document.getElementById('editTeamForm').action = '/teams/' + teamId + '/edit'; document.getElementById('edit_name').value = teamName; document.getElementById('edit_coach_id').value = coachId || ''; + document.getElementById('edit_manager_id').value = managerId || ''; document.getElementById('editTeamModal').classList.remove('hidden'); }); }); }); -function showEditForm(teamId, teamName, coachId) { +function showEditForm(teamId, teamName, coachId, managerId) { document.getElementById('editTeamForm').action = '/teams/' + teamId + '/edit'; document.getElementById('edit_name').value = teamName; document.getElementById('edit_coach_id').value = coachId || ''; + document.getElementById('edit_manager_id').value = managerId || ''; document.getElementById('editTeamModal').classList.remove('hidden'); } @@ -233,4 +325,22 @@ function hideEditForm() { document.getElementById('editTeamModal').classList.add('hidden'); } + {% endblock %} \ No newline at end of file