diff --git a/instance/team_tryouts.db b/instance/team_tryouts.db index 7ecf4ca..79db220 100644 Binary files a/instance/team_tryouts.db and b/instance/team_tryouts.db differ diff --git a/models.py b/models.py index 0371b34..55fb565 100644 --- a/models.py +++ b/models.py @@ -11,7 +11,7 @@ from urllib.parse import quote # Available user roles in the system -ROLES = ['president', 'manager', 'coach', 'player', 'scout'] +ROLES = ['admin', 'manager', 'coach', 'player', 'scout'] # Popular E-Sports games list for player profiles ESPORT_GAMES = [ @@ -123,7 +123,7 @@ class User(UserMixin, db.Model): Returns: bool: True if user is president, manager, or coach. """ - return self.role in ['president', 'manager', 'coach'] + return self.role in ['admin', 'manager', 'coach'] def can_manage_users(self): """Check if user can manage (create/delete) other users. @@ -131,7 +131,7 @@ class User(UserMixin, db.Model): Returns: bool: True if user is president. """ - return self.role == 'president' + return self.role == 'admin' def can_manage_tryouts(self): """Check if user can manage tryouts. @@ -139,7 +139,7 @@ class User(UserMixin, db.Model): Returns: bool: True if user is president, manager or coach. """ - return self.role in ['president', 'manager', 'coach'] + return self.role in ['admin', 'manager', 'coach'] def can_manage_teams(self): """Check if user can manage organization teams. @@ -147,7 +147,7 @@ class User(UserMixin, db.Model): Returns: bool: True if user is president or manager. """ - return self.role in ['president', 'manager'] + return self.role in ['admin', 'manager'] def can_schedule_matches(self): """Check if user can schedule matches. @@ -155,7 +155,7 @@ class User(UserMixin, db.Model): Returns: bool: True if user is president, manager, or coach. """ - return self.role in ['president', 'manager', 'coach'] + return self.role in ['admin', 'manager', 'coach'] def can_manage_this_tryout(self, tryout): """Check if user can manage a specific tryout. @@ -170,7 +170,7 @@ class User(UserMixin, db.Model): Returns: bool: True if user has permission to manage the tryout. """ - if self.role == 'president': + if self.role == 'admin': return True if self.role == 'manager' and (tryout.created_by == self.id or tryout.manager_id == self.id): return True @@ -203,7 +203,7 @@ class User(UserMixin, db.Model): Returns: bool: True if user has permission to manage the org team. """ - if self.role == 'president': + if self.role == 'admin': return True if self.role == 'manager': return True # Managers can manage all org teams (create/edit/delete) @@ -778,7 +778,7 @@ class Contract(db.Model): if user.id == self.player_id: return True # President can view all contracts - if user.role == 'president': + if user.role == 'admin': return True # Manager can view contracts for players on their teams if user.role == 'manager': diff --git a/routes/evaluations.py b/routes/evaluations.py index ceb619b..72a3fd3 100644 --- a/routes/evaluations.py +++ b/routes/evaluations.py @@ -89,7 +89,7 @@ def list_evaluations(): else: sort_expr = sort_expr.desc() - if user.role == 'president': + if user.role == 'admin': evaluations = Evaluation.query \ .outerjoin(Tryout, Evaluation.tryout_id == Tryout.id) \ .outerjoin(player_alias, Evaluation.player_id == player_alias.id) \ @@ -227,7 +227,7 @@ def evaluate_player(tryout_id, player_id): return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id)) evaluators = None - if current_user.role == 'president': + if current_user.role == 'admin': all_evaluations = Evaluation.query.filter_by(tryout_id=tryout_id, player_id=player_id).all() evaluators = [{'evaluator': User.query.get(e.evaluator_id), 'eval': e} for e in all_evaluations] diff --git a/routes/main.py b/routes/main.py index c160724..db2c91a 100644 --- a/routes/main.py +++ b/routes/main.py @@ -43,7 +43,7 @@ def dashboard(): user = current_user stats = {} - if user.role == 'president': + if user.role == 'admin': stats['total_users'] = User.query.count() stats['total_players'] = User.query.filter_by(role='player').count() stats['total_tryouts'] = Tryout.query.count() diff --git a/routes/matches.py b/routes/matches.py index 9a5947e..8c279c5 100644 --- a/routes/matches.py +++ b/routes/matches.py @@ -19,7 +19,7 @@ def can_schedule_match(): Returns: bool: True if user is president, manager, coach, or scout. """ - return current_user.role in ['president', 'manager', 'coach', 'scout'] + return current_user.role in ['admin', 'manager', 'coach', 'scout'] @matches_bp.route('/calendar') @@ -248,7 +248,7 @@ def get_visible_tryouts_for_user(): Returns: list: Query result of Tryout objects. """ - if current_user.role == 'president': + if current_user.role == 'admin': return Tryout.query.order_by(Tryout.date).all() elif current_user.role == 'manager': return Tryout.query.filter_by(created_by=current_user.id).order_by(Tryout.date).all() @@ -558,8 +558,10 @@ def edit_match(match_id): # Handle player vs player matches - update participants elif match.match_type == 'player_vs_player': MatchParticipant.query.filter_by(match_id=match.id).delete() - team1_player_ids = request.form.getlist('team1_player_ids') - team2_player_ids = request.form.getlist('team2_player_ids') + team1_str = request.form.get('team1_player_ids', '') + team2_str = request.form.get('team2_player_ids', '') + team1_player_ids = [p for p in team1_str.split(',') if p.strip()] if team1_str else [] + team2_player_ids = [p for p in team2_str.split(',') if p.strip()] if team2_str else [] notified_participant_ids = [] for pid in team1_player_ids: participant = MatchParticipant(match_id=match.id, player_id=int(pid), team_side=1) diff --git a/routes/team_matches.py b/routes/team_matches.py index bb7eaf1..f4f7130 100644 --- a/routes/team_matches.py +++ b/routes/team_matches.py @@ -20,7 +20,7 @@ def can_manage_team_match(team): Returns: bool: True if user is president, manager, or a coach of this team. """ - if current_user.role in ['president']: + if current_user.role in ['admin']: return True if current_user.role == 'manager': return True @@ -45,7 +45,7 @@ def list_matches(): # Optional pre-filter by team_id from query param filter_team_id = request.args.get('team_id', type=int) - if current_user.role == 'president': + if current_user.role == 'admin': teams = OrgTeam.query.order_by(OrgTeam.name).all() matches_query = TeamMatch.query elif current_user.role == 'manager': @@ -337,7 +337,7 @@ def api_manageable_teams(): if not current_user.can_schedule_matches(): return jsonify([]) - if current_user.role in ['president', 'manager']: + if current_user.role in ['admin', 'manager']: teams = OrgTeam.query.order_by(OrgTeam.name).all() elif current_user.role == 'coach': teams = OrgTeam.query.filter( diff --git a/routes/teams.py b/routes/teams.py index f427a15..6ba869e 100644 --- a/routes/teams.py +++ b/routes/teams.py @@ -24,7 +24,9 @@ def list_teams(): """ can_manage = current_user.can_manage_teams() - if current_user.role == 'coach': + if current_user.role == 'admin': + teams = OrgTeam.query.order_by(OrgTeam.name).all() + elif current_user.role == 'coach': teams = OrgTeam.query.filter( db.or_( OrgTeam.coaches.any(id=current_user.id), @@ -181,18 +183,45 @@ def edit_team(team_id): flash(f'Team "{name}" already exists.', 'danger') return redirect(url_for('teams.list_teams')) - 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 - - if coach_id: - coach_user = User.query.get(int(coach_id)) - if coach_user and not team.coaches.filter_by(id=coach_user.id).first(): - team.coaches.append(coach_user) - if manager_id: - manager_user = User.query.get(int(manager_id)) - if manager_user and not team.managers.filter_by(id=manager_user.id).first(): - team.managers.append(manager_user) + # Check if we're syncing staff (multi-select) or single legacy update + if request.form.get('sync_staff') == '1': + coach_ids = request.form.getlist('coach_ids') + manager_ids = request.form.getlist('manager_ids') + + # Sync coaches many-to-many + team.coaches = [] + for cid in coach_ids: + if cid and cid.strip(): + coach_user = User.query.get(int(cid)) + if coach_user and coach_user.role == 'coach': + team.coaches.append(coach_user) + # Update legacy coach_id with first coach + coach_list = team.coaches.all() + team.coach_id = coach_list[0].id if coach_list else None + + # Sync managers many-to-many + team.managers = [] + for mid in manager_ids: + if mid and mid.strip(): + manager_user = User.query.get(int(mid)) + if manager_user and manager_user.role == 'manager': + team.managers.append(manager_user) + # Update legacy manager_id with first manager + manager_list = team.managers.all() + team.manager_id = manager_list[0].id if manager_list else None + else: + # Legacy single dropdown update + team.coach_id = int(coach_id) if coach_id else None + team.manager_id = int(manager_id) if manager_id else None + + if coach_id: + coach_user = User.query.get(int(coach_id)) + if coach_user and not team.coaches.filter_by(id=coach_user.id).first(): + team.coaches.append(coach_user) + if manager_id: + manager_user = User.query.get(int(manager_id)) + if manager_user and not team.managers.filter_by(id=manager_user.id).first(): + team.managers.append(manager_user) db.session.commit() flash(f'Team "{name}" updated successfully!', 'success') diff --git a/routes/tryouts.py b/routes/tryouts.py index fa63f36..a4fac57 100644 --- a/routes/tryouts.py +++ b/routes/tryouts.py @@ -18,7 +18,7 @@ def can_manage(): Returns: bool: True if user is president or manager. """ - return current_user.role in ['president', 'manager'] + return current_user.role in ['admin', 'manager'] @tryouts_bp.route('') @@ -35,7 +35,7 @@ def list_tryouts(): Returns: Response: Rendered tryouts list template. """ - if current_user.role == 'president': + if current_user.role == 'admin': tryouts = Tryout.query.order_by(Tryout.date.desc()).all() elif current_user.role == 'manager': tryouts = Tryout.query.filter_by(created_by=current_user.id).order_by(Tryout.date.desc()).all() @@ -192,7 +192,7 @@ def view_tryout(tryout_id): # Check if user has permission to view this tryout can_view = False - if current_user.role == 'president': + if current_user.role == 'admin': can_view = True elif current_user.role == 'manager' and tryout.created_by == current_user.id: can_view = True @@ -276,6 +276,17 @@ def view_tryout(tryout_id): confirmed_count = sum(1 for p in all_participants if p.attendance_confirmed) total_count = len(all_participants) + # Build per-player presence data for toggle buttons + player_presence = [] + for p in all_participants: + if p.player: + player_presence.append({ + 'participant_id': p.id, + 'player_id': p.player_id, + 'player_name': p.player.username, + 'attendance_confirmed': p.attendance_confirmed + }) + if match.match_type == 'team_vs_team': participants = { 'team1': match.team1.name if match.team1 else 'TBD', @@ -299,7 +310,8 @@ def view_tryout(tryout_id): 'match': match, 'participants': participants, 'confirmed_count': confirmed_count, - 'total_count': total_count + 'total_count': total_count, + 'player_presence': player_presence }) return render_template('pages/view_tryout.html', diff --git a/routes/users.py b/routes/users.py index ecbb82d..cce8732 100644 --- a/routes/users.py +++ b/routes/users.py @@ -69,7 +69,7 @@ def list_users(): Returns: Response: Rendered users list template or redirect to dashboard. """ - if current_user.role != 'president': + if current_user.role != 'admin': flash('Only the president can manage users.', 'danger') return redirect(url_for('main.dashboard')) @@ -91,7 +91,7 @@ def edit_user(user_id): Returns: Response: Edit form or redirect to users list. """ - if current_user.role != 'president': + if current_user.role != 'admin': flash('Only the president can edit users.', 'danger') return redirect(url_for('main.dashboard')) @@ -149,7 +149,7 @@ def delete_user(user_id): Returns: Response: Redirect to users list with status message. """ - if current_user.role != 'president': + if current_user.role != 'admin': flash('Only the president can delete users.', 'danger') return redirect(url_for('main.dashboard')) @@ -229,7 +229,7 @@ def create_user(): Returns: Response: Create form or redirect to users list. """ - if current_user.role != 'president': + if current_user.role != 'admin': flash('Only the president can create users.', 'danger') return redirect(url_for('main.dashboard')) @@ -588,7 +588,7 @@ def can_manage_player_contract(user, player_id): bool: True if user has permission to manage the contract. """ # President can manage all contracts - if user.role == 'president': + if user.role == 'admin': return True # Manager can upload contracts for any player @@ -621,7 +621,7 @@ def list_contracts(): if current_user.role == 'player': # Players see their own contracts contracts = Contract.query.filter_by(player_id=current_user.id).order_by(Contract.uploaded_at.desc()).all() - elif current_user.role in ['president', 'manager', 'coach']: + elif current_user.role in ['admin', 'manager', 'coach']: # Superiors see contracts for players on their teams players = [] if current_user.role == 'coach': @@ -636,7 +636,7 @@ def list_contracts(): player_ids = [p.id for p in players] contracts = Contract.query.filter(Contract.player_id.in_(player_ids)).order_by(Contract.uploaded_at.desc()).all() - return render_template('pages/contracts.html', contracts=contracts, players=players if current_user.role in ['president', 'manager', 'coach'] else None) + return render_template('pages/contracts.html', contracts=contracts, players=players if current_user.role in ['admin', 'manager', 'coach'] else None) @users_bp.route('/contracts/upload', methods=['GET', 'POST']) @@ -650,7 +650,7 @@ def upload_contract(): Returns: Response: Upload form or redirect to contracts list. """ - if current_user.role not in ['president', 'manager', 'coach']: + if current_user.role not in ['admin', 'manager', 'coach']: flash('Only presidents, managers, and coaches can upload contracts.', 'danger') return redirect(url_for('users.list_contracts')) @@ -1395,7 +1395,7 @@ def add_personal_note(): Returns: Response: Create form or redirect to notes view. """ - if current_user.role not in ['coach', 'manager', 'president']: + if current_user.role not in ['coach', 'manager', 'admin']: flash('Only coaches and managers can add notes.', 'danger') return redirect(url_for('main.dashboard')) @@ -1407,7 +1407,7 @@ def add_personal_note(): 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)).order_by(User.username).all() if player_ids else [] - elif current_user.role in ['president', 'manager']: + elif current_user.role in ['admin', 'manager']: players = User.query.filter_by(role='player').order_by(User.username).all() # Get available matches and tryouts for context @@ -1417,7 +1417,7 @@ def add_personal_note(): if current_user.role == 'coach' and org_team: tryouts = Tryout.query.filter_by(target_org_team_id=org_team.id).order_by(Tryout.date.desc()).all() matches = Match.query.join(Tryout).filter(Tryout.target_org_team_id == org_team.id).order_by(Match.date.desc()).all() - elif current_user.role in ['president', 'manager']: + elif current_user.role in ['admin', 'manager']: tryouts = Tryout.query.order_by(Tryout.date.desc()).all() matches = Match.query.order_by(Match.date.desc()).all() teams = Team.query.order_by(Team.name).all() @@ -1501,7 +1501,7 @@ def add_note_from_match(match_id): Returns: Response: Create form or redirect. """ - if current_user.role not in ['coach', 'manager', 'president']: + if current_user.role not in ['coach', 'manager', 'admin']: flash('Only coaches can add notes from matches.', 'danger') return redirect(url_for('main.dashboard')) @@ -1577,7 +1577,7 @@ def add_note_from_tryout(tryout_id): Returns: Response: Create form or redirect. """ - if current_user.role not in ['coach', 'manager', 'president']: + if current_user.role not in ['coach', 'manager', 'admin']: flash('Only coaches can add notes from tryouts.', 'danger') return redirect(url_for('main.dashboard')) diff --git a/seed.py b/seed.py index f5d1604..2d4ada2 100644 --- a/seed.py +++ b/seed.py @@ -43,7 +43,7 @@ def seed_database(): # Create users with different roles users_data = [ - {'username': 'president', 'password': 'password', 'role': 'president', 'full_name': 'Sarah Johnson', 'email': 'sarah@teampro.com', 'phone': '555-0101'}, + {'username': 'admin', 'password': 'password', 'role': 'admin', 'full_name': 'Sarah Johnson', 'email': 'sarah@teampro.com', 'phone': '555-0101'}, {'username': 'manager1', 'password': 'password', 'role': 'manager', 'full_name': 'Mike Williams', 'email': 'mike@teampro.com', 'phone': '555-0102'}, {'username': 'manager2', 'password': 'password', 'role': 'manager', 'full_name': 'Emily Davis', 'email': 'emily@teampro.com', 'phone': '555-0103'}, {'username': 'coach1', 'password': 'password', 'role': 'coach', 'full_name': 'Coach Thompson', 'email': 'coach.t@teampro.com', 'phone': '555-0104', 'discord_user_id': '484107446298738689'}, @@ -117,7 +117,7 @@ def seed_database(): # Map users for easy access user_map = {u.username: u for u in users} - president = user_map['president'] + president = user_map['admin'] manager1 = user_map['manager1'] manager2 = user_map['manager2'] coaches = [user_map['coach1'], user_map['coach2'], user_map['coach3']] @@ -507,7 +507,7 @@ def seed_database(): print("\n[SUCCESS] Database seeded successfully!") print("\n=== Login Credentials ===") - print("President: username='president', password='password'") + print("President: username='admin', password='password'") print("Manager: username='manager1', password='password'") print("Coach: username='coach1', password='password' (assigned to Varsity)") print("Coach: username='coach2', password='password' (assigned to Junior Varsity)") diff --git a/static/css/style.css b/static/css/style.css index 129747e..847abf1 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -1231,9 +1231,9 @@ a:hover { color: var(--primary-dark); } } .merged-disponibility-time-block.selected { - background: var(--success); - color: white; - border-color: var(--success); + background: #6366f1 !important; + color: white !important; + border-color: #4f46e5 !important; } .merged-disponibility-time-block.high-availability { @@ -1266,10 +1266,10 @@ a:hover { color: var(--primary-dark); } /* Then specific states override the generic rule */ [data-theme="dark"] .merged-disponibility-time-block.selected { - background: var(--success) !important; + background: #6366f1 !important; color: white !important; - border-color: var(--success) !important; - box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.5); + border-color: #818cf8 !important; + box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.5); } [data-theme="dark"] .merged-disponibility-time-block.high-availability { diff --git a/templates/pages/contracts.html b/templates/pages/contracts.html index 1202bd8..d275fcb 100644 --- a/templates/pages/contracts.html +++ b/templates/pages/contracts.html @@ -4,7 +4,7 @@ {% block breadcrumb %}{% endblock %} {% block header_actions %} -{% if current_user.role in ['president', 'manager', 'coach'] %} +{% if current_user.role in ['admin', 'manager', 'coach'] %} Upload Contract diff --git a/templates/pages/dashboard.html b/templates/pages/dashboard.html index 6ae7e90..bf20877 100644 --- a/templates/pages/dashboard.html +++ b/templates/pages/dashboard.html @@ -5,7 +5,7 @@ {% block content %}