first commit

This commit is contained in:
cedrick2711
2026-07-14 09:50:45 -04:00
commit 4fd27bacfd
43 changed files with 4427 additions and 0 deletions
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+54
View File
@@ -0,0 +1,54 @@
import os
from flask import Flask
from extensions import db, login_manager, csrf, hash_password, check_password
from sqlalchemy import text
def create_app():
app = Flask(__name__)
app.config['SECRET_KEY'] = os.environ.get('SECRET_KEY', 'team-tryouts-secret-key-change-in-production')
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///team_tryouts.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['WTF_CSRF_ENABLED'] = True
db.init_app(app)
login_manager.init_app(app)
csrf.init_app(app)
from routes.auth import auth_bp
from routes.tryouts import tryouts_bp
from routes.evaluations import evaluations_bp
from routes.users import users_bp
from routes.main import main_bp
from routes.teams import teams_bp
app.register_blueprint(auth_bp)
app.register_blueprint(tryouts_bp)
app.register_blueprint(evaluations_bp)
app.register_blueprint(users_bp)
app.register_blueprint(main_bp)
app.register_blueprint(teams_bp)
with app.app_context():
import models
from models import User
try:
# Check if the database schema is up to date by testing a query
# that uses all model columns
db.session.execute(text('SELECT games FROM users LIMIT 1'))
db.create_all()
except Exception:
# If there's a schema mismatch, drop and recreate all tables
db.session.rollback()
db.drop_all()
db.create_all()
# Seed database if empty
if User.query.count() == 0:
from seed import seed_database
seed_database()
return app
if __name__ == '__main__':
app = create_app()
app.run(debug=True, host='0.0.0.0', port=5000)
+147
View File
@@ -0,0 +1,147 @@
{% extends "layouts/base.html" %}
{% block title %}My Profile - TryoutPro{% endblock %}
{% block page_title %}My Profile{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / Profile</span>{% endblock %}
{% block header_actions %}
<div class="header-actions">
<a href="{{ url_for('users.edit_profile') }}" class="btn btn-sm btn-primary">
<i class="fas fa-edit"></i> Edit Profile
</a>
</div>
{% endblock %}
{% block content %}
<div class="dashboard-grid">
<div class="card">
<div class="card-header">
<h3><i class="fas fa-user-circle"></i> Account Information</h3>
</div>
<div class="card-body">
<div class="profile-header">
<div class="user-avatar avatar-xl">{{ user.full_name[:2] | upper }}</div>
<div class="profile-info">
<h2>{{ user.full_name }}</h2>
<span class="badge badge-{{ user.role }} badge-lg">{{ user.role | capitalize }}</span>
<p class="text-muted">Member since {% if user.created_at %}{{ user.created_at.strftime('%B %Y') }}{% else %}Unknown{% endif %}</p>
</div>
</div>
<div class="detail-grid mt-4">
<div class="detail-item">
<span class="detail-label">Username</span>
<span class="detail-value">{{ user.username }}</span>
</div>
<div class="detail-item">
<span class="detail-label">Email</span>
<span class="detail-value">{{ user.email }}</span>
</div>
<div class="detail-item">
<span class="detail-label">Phone</span>
<span class="detail-value">{{ user.phone or 'Not provided' }}</span>
</div>
<div class="detail-item">
<span class="detail-label">Account Status</span>
<span class="detail-value">
{% if user.is_active_account %}
<span class="badge badge-success">Active</span>
{% else %}
<span class="badge badge-danger">Inactive</span>
{% endif %}
</span>
</div>
</div>
</div>
</div>
<!-- E-Sports Profile Card -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-gamepad"></i> E-Sports Profile</h3>
</div>
<div class="card-body">
<div class="detail-grid">
<div class="detail-item">
<span class="detail-label"><i class="fas fa-headset"></i> Games</span>
<span class="detail-value">
{% set games_list = user.get_games_list() %}
{% if games_list %}
{% for game in games_list %}
<span class="badge badge-esport">{{ game }}</span>
{% endfor %}
{% else %}
<span class="text-muted">Not specified</span>
{% endif %}
</span>
</div>
<div class="detail-item">
<span class="detail-label"><i class="fas fa-chart-line"></i> TRN (Tracker Network)</span>
<span class="detail-value">
{% if user.trn_username %}
<a href="https://tracker.gg" target="_blank" rel="noopener noreferrer" class="trn-link">
<i class="fas fa-external-link-alt"></i> {{ user.trn_username }}
</a>
{% else %}
<span class="text-muted">Not connected</span>
{% endif %}
</span>
</div>
<div class="detail-item">
<span class="detail-label"><i class="fab fa-discord"></i> Discord</span>
<span class="detail-value">
{% if user.discord_username %}
{{ user.discord_username }}
{% else %}
<span class="text-muted">Not connected</span>
{% endif %}
</span>
</div>
<div class="detail-item">
<span class="detail-label"><i class="fas fa-link"></i> League OS</span>
<span class="detail-value">
{% if user.league_os_profile %}
<a href="{{ user.league_os_profile }}" target="_blank" rel="noopener noreferrer" class="trn-link">
<i class="fas fa-external-link-alt"></i> {{ user.league_os_profile }}
</a>
{% else %}
<span class="text-muted">Not connected</span>
{% endif %}
</span>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<h3><i class="fas fa-chart-bar"></i> My Statistics</h3>
</div>
<div class="card-body">
{% if user.role == 'player' %}
<div class="profile-stats">
<div class="stat-item">
<h4>{{ user.tryout_registrations.count() }}</h4>
<p>Tryouts Registered</p>
</div>
<div class="stat-item">
<h4>{{ user.evaluations_received.count() }}</h4>
<p>Evaluations Received</p>
</div>
<div class="stat-item">
<h4>{{ user.team_assignments.count() }}</h4>
<p>Team Assignments</p>
</div>
</div>
{% elif user.can_evaluate() %}
<div class="profile-stats">
<div class="stat-item">
<h4>{{ user.evaluations_given.count() }}</h4>
<p>Evaluations Given</p>
</div>
</div>
{% else %}
<p class="text-muted">No statistics available for this role.</p>
{% endif %}
</div>
</div>
</div>
{% endblock %}
+16
View File
@@ -0,0 +1,16 @@
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager
from flask_wtf.csrf import CSRFProtect
from werkzeug.security import generate_password_hash, check_password_hash
db = SQLAlchemy()
login_manager = LoginManager()
login_manager.login_view = 'auth.login'
login_manager.login_message_category = 'info'
csrf = CSRFProtect()
def hash_password(password):
return generate_password_hash(password)
def check_password(password_hash, password):
return check_password_hash(password_hash, password)
Binary file not shown.
+161
View File
@@ -0,0 +1,161 @@
from extensions import db, login_manager
from flask_login import UserMixin
from datetime import datetime
ROLES = ['president', 'manager', 'coach', 'player', 'scout']
# Popular E-Sports games list
ESPORT_GAMES = [
'Valorant',
'League of Legends',
'Counter-Strike 2',
'Apex Legends',
'Overwatch 2',
'Rainbow Six Siege',
'Fortnite',
'Rocket League',
'Call of Duty',
'Dota 2',
'Super Smash Bros.',
'Street Fighter 6',
]
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(80), unique=True, nullable=False)
password_hash = db.Column(db.String(128), nullable=False)
role = db.Column(db.String(20), nullable=False, default='player')
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)
# E-Sports specific fields
games = db.Column(db.Text, nullable=True) # Comma-separated list of games
trn_username = db.Column(db.String(120), nullable=True) # Tracker Network username
discord_username = db.Column(db.String(100), nullable=True) # Discord handle
league_os_profile = db.Column(db.String(255), nullable=True) # League OS profile URL or ID
evaluations_given = db.relationship('Evaluation', foreign_keys='Evaluation.evaluator_id', backref='evaluator', lazy='dynamic')
evaluations_received = db.relationship('Evaluation', foreign_keys='Evaluation.player_id', backref='player', lazy='dynamic')
tryout_registrations = db.relationship('TryoutRegistration', backref='player', lazy='dynamic')
team_assignments = db.relationship('TeamMember', foreign_keys='TeamMember.player_id', backref='player_ref', lazy='dynamic')
def get_games_list(self):
"""Return games as a list."""
if self.games:
return [g.strip() for g in self.games.split(',') if g.strip()]
return []
def has_role(self, *roles):
return self.role in roles
def can_evaluate(self):
return self.role in ['president', 'manager', 'coach']
def can_manage_users(self):
return self.role == 'president'
def can_manage_tryouts(self):
return self.role in ['president', 'manager']
def can_manage_teams(self):
return self.role in ['president', 'manager']
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
class OrgTeam(db.Model):
"""Persistent organization teams (e.g., Varsity, JV) that exist across tryouts."""
__tablename__ = 'org_teams'
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)
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)
creator = db.relationship('User', foreign_keys=[created_by])
players = db.relationship('User', foreign_keys='User.team_id', backref='org_team', lazy='dynamic')
class Tryout(db.Model):
__tablename__ = 'tryouts'
id = db.Column(db.Integer, primary_key=True)
title = db.Column(db.String(200), nullable=False)
description = db.Column(db.Text, nullable=True)
date = db.Column(db.Date, nullable=False)
location = db.Column(db.String(200), nullable=True)
status = db.Column(db.String(20), default='upcoming') # upcoming, in_progress, completed
max_players = db.Column(db.Integer, nullable=True)
created_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
target_org_team_id = db.Column(db.Integer, db.ForeignKey('org_teams.id'), nullable=True)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
creator = db.relationship('User', backref='created_tryouts')
registrations = db.relationship('TryoutRegistration', backref='tryout', lazy='dynamic')
evaluations = db.relationship('Evaluation', backref='tryout', lazy='dynamic')
teams = db.relationship('Team', backref='tryout', lazy='dynamic')
target_org_team = db.relationship('OrgTeam', backref='tryouts', foreign_keys=[target_org_team_id])
class TryoutRegistration(db.Model):
__tablename__ = 'tryout_registrations'
id = db.Column(db.Integer, primary_key=True)
tryout_id = db.Column(db.Integer, db.ForeignKey('tryouts.id'), nullable=False)
player_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
registered_at = db.Column(db.DateTime, default=datetime.utcnow)
status = db.Column(db.String(20), default='registered') # registered, attended, no_show
notes = db.Column(db.Text, nullable=True)
class Evaluation(db.Model):
__tablename__ = 'evaluations'
id = db.Column(db.Integer, primary_key=True)
tryout_id = db.Column(db.Integer, db.ForeignKey('tryouts.id'), nullable=False)
player_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
evaluator_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
speed_score = db.Column(db.Integer, nullable=True)
agility_score = db.Column(db.Integer, nullable=True)
technique_score = db.Column(db.Integer, nullable=True)
teamwork_score = db.Column(db.Integer, nullable=True)
attitude_score = db.Column(db.Integer, nullable=True)
overall_score = db.Column(db.Float, nullable=True)
comments = db.Column(db.Text, nullable=True)
position_recommendation = db.Column(db.String(50), nullable=True)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
__table_args__ = (
db.UniqueConstraint('tryout_id', 'player_id', 'evaluator_id', name='unique_evaluation'),
)
class Team(db.Model):
"""Tryout-specific teams (e.g., Alpha, Bravo within a single tryout)."""
__tablename__ = 'teams'
id = db.Column(db.Integer, primary_key=True)
tryout_id = db.Column(db.Integer, db.ForeignKey('tryouts.id'), nullable=False)
name = db.Column(db.String(100), nullable=False)
created_by = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
created_at = db.Column(db.DateTime, default=datetime.utcnow)
creator = db.relationship('User', backref='created_teams')
members = db.relationship('TeamMember', backref='team', lazy='dynamic')
class TeamMember(db.Model):
__tablename__ = 'team_members'
id = db.Column(db.Integer, primary_key=True)
team_id = db.Column(db.Integer, db.ForeignKey('teams.id'), nullable=False)
player_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
position = db.Column(db.String(50), nullable=True)
added_at = db.Column(db.DateTime, default=datetime.utcnow)
player = db.relationship('User', overlaps="player_ref,team_assignments") # Many-to-one, no dynamic loader
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+88
View File
@@ -0,0 +1,88 @@
from flask import Blueprint, render_template, redirect, url_for, flash, request
from flask_login import login_user, logout_user, login_required, current_user
from extensions import db, hash_password, check_password
from models import User, ESPORT_GAMES
auth_bp = Blueprint('auth', __name__, url_prefix='/auth')
@auth_bp.route('/login', methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('main.dashboard'))
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
user = User.query.filter_by(username=username).first()
if user and check_password(user.password_hash, password):
if not user.is_active_account:
flash('This account has been deactivated.', 'danger')
return render_template('pages/login.html')
login_user(user)
next_page = request.args.get('next')
flash(f'Welcome back, {user.full_name}!', 'success')
return redirect(next_page) if next_page else redirect(url_for('main.dashboard'))
else:
flash('Login unsuccessful. Please check username and password.', 'danger')
return render_template('pages/login.html')
@auth_bp.route('/register', methods=['GET', 'POST'])
def register():
if current_user.is_authenticated:
return redirect(url_for('main.dashboard'))
if request.method == 'POST':
username = request.form.get('username')
email = request.form.get('email')
password = request.form.get('password')
confirm_password = request.form.get('confirm_password')
full_name = request.form.get('full_name')
phone = request.form.get('phone')
# E-Sports fields
selected_games = request.form.getlist('games')
trn_username = request.form.get('trn_username', '').strip()
discord_username = request.form.get('discord_username', '').strip()
league_os_profile = request.form.get('league_os_profile', '').strip()
if password != confirm_password:
flash('Passwords do not match.', 'danger')
return render_template('pages/register.html', esport_games=ESPORT_GAMES)
if User.query.filter_by(username=username).first():
flash('Username already exists.', 'danger')
return render_template('pages/register.html', esport_games=ESPORT_GAMES)
if User.query.filter_by(email=email).first():
flash('Email already registered.', 'danger')
return render_template('pages/register.html', esport_games=ESPORT_GAMES)
hashed_password = hash_password(password)
user = User(
username=username,
password_hash=hashed_password,
role='player',
full_name=full_name,
email=email,
phone=phone,
games=','.join(selected_games) if selected_games else None,
trn_username=trn_username or None,
discord_username=discord_username or None,
league_os_profile=league_os_profile or None
)
db.session.add(user)
db.session.commit()
flash('Your account has been created! You can now log in.', 'success')
return redirect(url_for('auth.login'))
return render_template('pages/register.html', esport_games=ESPORT_GAMES)
@auth_bp.route('/logout')
@login_required
def logout():
logout_user()
flash('You have been logged out.', 'info')
return redirect(url_for('auth.login'))
+135
View File
@@ -0,0 +1,135 @@
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 User, Tryout, Evaluation, TryoutRegistration
from sqlalchemy import func
evaluations_bp = Blueprint('evaluations', __name__, url_prefix='/evaluations')
@evaluations_bp.route('')
@login_required
def list_evaluations():
user = current_user
if user.role == 'president':
evaluations = Evaluation.query.order_by(Evaluation.created_at.desc()).all()
avg_scores = db.session.query(
Evaluation.player_id,
func.count(Evaluation.id).label('eval_count'),
func.avg(Evaluation.overall_score).label('avg_score')
).group_by(Evaluation.player_id).all()
player_scores = {}
for row in avg_scores:
p = User.query.get(row.player_id)
if p:
player_scores[p.id] = {'player': p, 'count': row.eval_count, 'avg': round(row.avg_score, 1) if row.avg_score else 0}
elif user.can_evaluate():
evaluations = Evaluation.query.filter_by(evaluator_id=user.id).order_by(Evaluation.created_at.desc()).all()
player_scores = {}
else:
evaluations = Evaluation.query.filter_by(player_id=user.id).order_by(Evaluation.created_at.desc()).all()
player_scores = {}
return render_template('pages/evaluations.html', evaluations=evaluations, player_scores=player_scores)
@evaluations_bp.route('/<int:tryout_id>/<int:player_id>', methods=['GET', 'POST'])
@login_required
def evaluate_player(tryout_id, player_id):
if not current_user.can_evaluate():
flash('You do not have permission to evaluate players.', 'danger')
return redirect(url_for('main.dashboard'))
tryout = Tryout.query.get_or_404(tryout_id)
player = User.query.get_or_404(player_id)
if player.role != 'player':
flash('Can only evaluate players.', 'danger')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
existing_eval = Evaluation.query.filter_by(
tryout_id=tryout_id,
player_id=player_id,
evaluator_id=current_user.id
).first()
if request.method == 'POST':
speed = request.form.get('speed_score')
agility = request.form.get('agility_score')
technique = request.form.get('technique_score')
teamwork = request.form.get('teamwork_score')
attitude = request.form.get('attitude_score')
comments = request.form.get('comments')
position = request.form.get('position_recommendation')
scores = []
if speed: scores.append(int(speed))
if agility: scores.append(int(agility))
if technique: scores.append(int(technique))
if teamwork: scores.append(int(teamwork))
if attitude: scores.append(int(attitude))
overall = sum(scores) / len(scores) if scores else None
if existing_eval:
existing_eval.speed_score = int(speed) if speed else None
existing_eval.agility_score = int(agility) if agility else None
existing_eval.technique_score = int(technique) if technique else None
existing_eval.teamwork_score = int(teamwork) if teamwork else None
existing_eval.attitude_score = int(attitude) if attitude else None
existing_eval.overall_score = overall
existing_eval.comments = comments
existing_eval.position_recommendation = position
flash('Evaluation updated!', 'success')
else:
evaluation = Evaluation(
tryout_id=tryout_id,
player_id=player_id,
evaluator_id=current_user.id,
speed_score=int(speed) if speed else None,
agility_score=int(agility) if agility else None,
technique_score=int(technique) if technique else None,
teamwork_score=int(teamwork) if teamwork else None,
attitude_score=int(attitude) if attitude else None,
overall_score=overall,
comments=comments,
position_recommendation=position
)
db.session.add(evaluation)
flash('Evaluation submitted successfully!', 'success')
db.session.commit()
return redirect(url_for('evaluations.list_evaluations'))
evaluators = None
if current_user.role == 'president':
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]
return render_template('pages/evaluate_player.html',
tryout=tryout,
player=player,
existing_eval=existing_eval,
evaluators=evaluators)
@evaluations_bp.route('/<int:tryout_id>/players')
@login_required
def players_to_evaluate(tryout_id):
if not current_user.can_evaluate():
flash('Permission denied.', 'danger')
return redirect(url_for('main.dashboard'))
tryout = Tryout.query.get_or_404(tryout_id)
registrations = TryoutRegistration.query.filter_by(tryout_id=tryout_id).all()
players = []
for reg in registrations:
p = User.query.get(reg.player_id)
if p and p.role == 'player':
existing = Evaluation.query.filter_by(
tryout_id=tryout_id,
player_id=p.id,
evaluator_id=current_user.id
).first()
players.append({'player': p, 'evaluated': existing is not None, 'registration': reg})
return render_template('pages/players_to_evaluate.html', tryout=tryout, players=players)
+64
View File
@@ -0,0 +1,64 @@
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 sqlalchemy import func
main_bp = Blueprint('main', __name__)
@main_bp.route('/')
def index():
return redirect(url_for('auth.login'))
@main_bp.route('/dashboard')
@login_required
def dashboard():
user = current_user
stats = {}
if user.role == 'president':
stats['total_users'] = User.query.count()
stats['total_players'] = User.query.filter_by(role='player').count()
stats['total_tryouts'] = Tryout.query.count()
stats['total_evaluations'] = Evaluation.query.count()
stats['active_tryouts'] = Tryout.query.filter_by(status='in_progress').count()
stats['upcoming_tryouts'] = Tryout.query.filter_by(status='upcoming').count()
stats['recent_users'] = User.query.order_by(User.created_at.desc()).limit(5).all()
stats['recent_tryouts'] = Tryout.query.order_by(Tryout.created_at.desc()).limit(5).all()
elif user.role == 'manager':
stats['total_tryouts'] = Tryout.query.filter_by(created_by=user.id).count()
stats['active_tryouts'] = Tryout.query.filter_by(created_by=user.id, status='in_progress').count()
stats['total_evaluations'] = Evaluation.query.filter_by(evaluator_id=user.id).count()
stats['my_tryouts'] = Tryout.query.filter_by(created_by=user.id).order_by(Tryout.date.desc()).limit(5).all()
elif user.role == 'coach':
stats['my_evaluations'] = Evaluation.query.filter_by(evaluator_id=user.id).count()
stats['pending_evaluations'] = 0
registrations = TryoutRegistration.query.filter(TryoutRegistration.status.in_(['registered', 'attended'])).all()
registered_player_ids = [r.player_id for r in registrations]
evaluated_player_ids = [e.player_id for e in Evaluation.query.filter_by(evaluator_id=user.id).all()]
stats['pending_evaluations'] = len(set(registered_player_ids) - set(evaluated_player_ids))
stats['my_recent_evaluations'] = Evaluation.query.filter_by(evaluator_id=user.id).order_by(Evaluation.created_at.desc()).limit(5).all()
elif user.role == 'player':
stats['my_tryouts'] = TryoutRegistration.query.filter_by(player_id=user.id).count()
stats['my_evaluations'] = Evaluation.query.filter_by(player_id=user.id).count()
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()
elif user.role == 'scout':
stats['total_players'] = User.query.filter_by(role='player').count()
stats['total_evaluations'] = Evaluation.query.count()
stats['avg_scores'] = db.session.query(
Evaluation.player_id,
func.avg(Evaluation.overall_score).label('avg_score')
).group_by(Evaluation.player_id).order_by(func.avg(Evaluation.overall_score).desc()).limit(5).all()
stats['top_players'] = []
for row in stats['avg_scores']:
p = User.query.get(row.player_id)
if p:
stats['top_players'].append((p, round(row.avg_score, 1)))
return render_template('pages/dashboard.html', user=user, stats=stats)
+155
View File
@@ -0,0 +1,155 @@
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 OrgTeam, User
teams_bp = Blueprint('teams', __name__, url_prefix='/teams')
@teams_bp.route('')
@login_required
def list_teams():
can_manage = current_user.can_manage_teams()
if current_user.role == 'coach':
org_team = OrgTeam.query.filter_by(coach_id=current_user.id).first()
teams = [org_team] if org_team else []
elif current_user.role in ['player', 'scout'] or can_manage:
# Players and scouts can view all teams; managers/presidents can manage
teams = OrgTeam.query.order_by(OrgTeam.name).all()
else:
flash('You do not have permission to view teams.', 'danger')
return redirect(url_for('main.dashboard'))
coaches = User.query.filter_by(role='coach').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)
@teams_bp.route('/create', methods=['POST'])
@login_required
def create_team():
if not current_user.can_manage_teams():
flash('You do not have permission to create teams.', 'danger')
return redirect(url_for('teams.list_teams'))
name = request.form.get('name')
coach_id = request.form.get('coach_id')
if not name:
flash('Team name is required.', 'danger')
return redirect(url_for('teams.list_teams'))
existing = OrgTeam.query.filter_by(name=name).first()
if existing:
flash(f'Team "{name}" already exists.', 'danger')
return redirect(url_for('teams.list_teams'))
team = OrgTeam(
name=name,
coach_id=int(coach_id) if coach_id else None,
created_by=current_user.id
)
db.session.add(team)
db.session.commit()
flash(f'Team "{name}" created successfully!', 'success')
return redirect(url_for('teams.list_teams'))
@teams_bp.route('/<int:team_id>/edit', methods=['POST'])
@login_required
def edit_team(team_id):
if not current_user.can_manage_teams():
flash('You do not have permission to edit teams.', 'danger')
return redirect(url_for('teams.list_teams'))
team = OrgTeam.query.get_or_404(team_id)
name = request.form.get('name')
coach_id = request.form.get('coach_id')
if not name:
flash('Team name is required.', 'danger')
return redirect(url_for('teams.list_teams'))
existing = OrgTeam.query.filter(OrgTeam.name == name, OrgTeam.id != team_id).first()
if existing:
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
db.session.commit()
flash(f'Team "{name}" updated successfully!', 'success')
return redirect(url_for('teams.list_teams'))
@teams_bp.route('/<int:team_id>/delete', methods=['POST'])
@login_required
def delete_team(team_id):
if not current_user.can_manage_teams():
flash('You do not have permission to delete teams.', 'danger')
return redirect(url_for('teams.list_teams'))
team = OrgTeam.query.get_or_404(team_id)
name = team.name
# Check if any tryouts are targeting this team
from models import Tryout
tryouts = Tryout.query.filter_by(target_org_team_id=team_id).all()
if tryouts:
for t in tryouts:
t.target_org_team_id = None
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('/<int:team_id>/add_player', methods=['POST'])
@login_required
def add_player(team_id):
if not current_user.can_manage_teams():
flash('Permission denied.', 'danger')
return redirect(url_for('teams.list_teams'))
team = OrgTeam.query.get_or_404(team_id)
player_id = request.form.get('player_id')
position = request.form.get('position', '')
if not player_id:
flash('Please select a player.', 'danger')
return redirect(url_for('teams.list_teams'))
player = User.query.get_or_404(int(player_id))
if player.role != 'player':
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:
flash(f'{player.full_name} is already on {team.name}.', 'info')
return redirect(url_for('teams.list_teams'))
player.team_id = team_id
db.session.commit()
flash(f'{player.full_name} added to {team.name}!', 'success')
return redirect(url_for('teams.list_teams'))
@teams_bp.route('/<int:team_id>/remove_player/<int:player_id>', methods=['POST'])
@login_required
def remove_player(team_id, player_id):
if not current_user.can_manage_teams():
flash('Permission denied.', 'danger')
return redirect(url_for('teams.list_teams'))
team = OrgTeam.query.get_or_404(team_id)
player = User.query.get_or_404(player_id)
if player.team_id != team_id:
flash(f'{player.full_name} is not on {team.name}.', 'danger')
return redirect(url_for('teams.list_teams'))
player.team_id = None
db.session.commit()
flash(f'{player.full_name} removed from {team.name}.', 'success')
return redirect(url_for('teams.list_teams'))
+310
View File
@@ -0,0 +1,310 @@
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, TryoutRegistration, Evaluation, Team, TeamMember, OrgTeam
from datetime import datetime
tryouts_bp = Blueprint('tryouts', __name__, url_prefix='/tryouts')
def can_manage():
return current_user.role in ['president', 'manager']
@tryouts_bp.route('')
@login_required
def list_tryouts():
if current_user.role == 'president':
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()
elif current_user.role == 'coach':
# Coaches see tryouts targeting their org team
org_team = OrgTeam.query.filter_by(coach_id=current_user.id).first()
if org_team:
tryouts = Tryout.query.filter_by(target_org_team_id=org_team.id).order_by(Tryout.date.desc()).all()
else:
tryouts = []
elif current_user.role == 'player':
tryouts = Tryout.query.filter(Tryout.status.in_(['upcoming', 'in_progress'])).order_by(Tryout.date.desc()).all()
else:
tryouts = Tryout.query.order_by(Tryout.date.desc()).all()
return render_template('pages/tryouts.html', tryouts=tryouts, now=datetime.utcnow())
@tryouts_bp.route('/create', methods=['GET', 'POST'])
@login_required
def create_tryout():
if not can_manage():
flash('You do not have permission to create tryouts.', 'danger')
return redirect(url_for('tryouts.list_tryouts'))
org_teams = OrgTeam.query.order_by(OrgTeam.name).all()
if request.method == 'POST':
title = request.form.get('title')
description = request.form.get('description')
date_str = request.form.get('date')
location = request.form.get('location')
max_players = request.form.get('max_players')
target_org_team_id = request.form.get('target_org_team_id')
try:
date_obj = datetime.strptime(date_str, '%Y-%m-%d').date()
except (ValueError, TypeError):
flash('Invalid date format.', 'danger')
return render_template('pages/create_tryout.html', org_teams=org_teams)
tryout = Tryout(
title=title,
description=description,
date=date_obj,
location=location,
max_players=int(max_players) if max_players else None,
created_by=current_user.id,
status='upcoming',
target_org_team_id=int(target_org_team_id) if target_org_team_id else None
)
db.session.add(tryout)
db.session.commit()
flash('Tryout created successfully!', 'success')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout.id))
return render_template('pages/create_tryout.html', org_teams=org_teams)
@tryouts_bp.route('/<int:tryout_id>/edit', methods=['GET', 'POST'])
@login_required
def edit_tryout(tryout_id):
tryout = Tryout.query.get_or_404(tryout_id)
# Permission: president can edit any; manager can edit own; coach can edit if it targets their team
if current_user.role == 'president':
pass # allowed
elif current_user.role == 'manager' and tryout.created_by == current_user.id:
pass # allowed
elif current_user.role == 'coach':
org_team = OrgTeam.query.filter_by(coach_id=current_user.id).first()
if not org_team or tryout.target_org_team_id != org_team.id:
flash('You do not have permission to edit this tryout.', 'danger')
return redirect(url_for('tryouts.list_tryouts'))
else:
flash('You do not have permission to edit this tryout.', 'danger')
return redirect(url_for('tryouts.list_tryouts'))
org_teams = OrgTeam.query.order_by(OrgTeam.name).all()
if request.method == 'POST':
title = request.form.get('title')
description = request.form.get('description')
date_str = request.form.get('date')
location = request.form.get('location')
max_players = request.form.get('max_players')
target_org_team_id = request.form.get('target_org_team_id')
try:
date_obj = datetime.strptime(date_str, '%Y-%m-%d').date()
except (ValueError, TypeError):
flash('Invalid date format.', 'danger')
return render_template('pages/edit_tryout.html', tryout=tryout, org_teams=org_teams)
tryout.title = title
tryout.description = description
tryout.date = date_obj
tryout.location = location
tryout.max_players = int(max_players) if max_players else None
tryout.target_org_team_id = int(target_org_team_id) if target_org_team_id else None
db.session.commit()
flash('Tryout updated successfully!', 'success')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout.id))
return render_template('pages/edit_tryout.html', tryout=tryout, org_teams=org_teams)
@tryouts_bp.route('/<int:tryout_id>')
@login_required
def view_tryout(tryout_id):
tryout = Tryout.query.get_or_404(tryout_id)
registrations = TryoutRegistration.query.filter_by(tryout_id=tryout_id).all()
registered_players = [User.query.get(r.player_id) for r in registrations if r.player_id]
evaluations = Evaluation.query.filter_by(tryout_id=tryout_id).all()
evaluator_ids = set(e.evaluator_id for e in evaluations)
player_ids_with_eval = set(e.player_id for e in evaluations)
# Check if current user has evaluated each player
player_eval_status = {}
if current_user.can_evaluate():
for p in registered_players:
existing = Evaluation.query.filter_by(
tryout_id=tryout_id,
player_id=p.id,
evaluator_id=current_user.id
).first()
player_eval_status[p.id] = existing is not None
is_registered = TryoutRegistration.query.filter_by(
tryout_id=tryout_id, player_id=current_user.id
).first() is not None
teams = Team.query.filter_by(tryout_id=tryout_id).all()
team_data = []
for team in teams:
members = TeamMember.query.filter_by(team_id=team.id).all()
team_data.append({
'team': team,
'members': [{'player': User.query.get(m.player_id), 'position': m.position} for m in members]
})
# Determine if current user can edit this tryout
can_edit = False
if current_user.role == 'president':
can_edit = True
elif current_user.role == 'manager' and tryout.created_by == current_user.id:
can_edit = True
elif current_user.role == 'coach':
org_team = OrgTeam.query.filter_by(coach_id=current_user.id).first()
if org_team and tryout.target_org_team_id == org_team.id:
can_edit = True
# Get all players (for manager registration dropdown)
all_players = User.query.filter_by(role='player').order_by(User.full_name).all()
return render_template('pages/view_tryout.html',
tryout=tryout,
registered_players=registered_players,
evaluations=evaluations,
player_eval_status=player_eval_status,
is_registered=is_registered,
registrations=registrations,
team_data=team_data,
can_edit=can_edit,
all_players=all_players,
now=datetime.utcnow())
@tryouts_bp.route('/<int:tryout_id>/register', methods=['POST'])
@login_required
def register_for_tryout(tryout_id):
tryout = Tryout.query.get_or_404(tryout_id)
if current_user.role != 'player':
flash('Only players can register for tryouts.', 'danger')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
if tryout.status not in ['upcoming', 'in_progress']:
flash('This tryout is not accepting registrations.', 'danger')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
existing = TryoutRegistration.query.filter_by(tryout_id=tryout_id, player_id=current_user.id).first()
if existing:
flash('You are already registered for this tryout.', 'info')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
if tryout.max_players:
count = TryoutRegistration.query.filter_by(tryout_id=tryout_id).count()
if count >= tryout.max_players:
flash('This tryout is full.', 'danger')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
registration = TryoutRegistration(tryout_id=tryout_id, player_id=current_user.id)
db.session.add(registration)
db.session.commit()
flash('Successfully registered for tryout!', 'success')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
@tryouts_bp.route('/<int:tryout_id>/status', methods=['POST'])
@login_required
def update_status(tryout_id):
if not can_manage():
flash('Permission denied.', 'danger')
return redirect(url_for('tryouts.list_tryouts'))
tryout = Tryout.query.get_or_404(tryout_id)
new_status = request.form.get('status')
if new_status in ['upcoming', 'in_progress', 'completed']:
tryout.status = new_status
db.session.commit()
flash(f'Tryout status updated to {new_status}.', 'success')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
@tryouts_bp.route('/<int:tryout_id>/registration/<int:player_id>/status', methods=['POST'])
@login_required
def update_registration_status(tryout_id, player_id):
if not can_manage():
flash('Permission denied.', 'danger')
return redirect(url_for('tryouts.list_tryouts'))
registration = TryoutRegistration.query.filter_by(tryout_id=tryout_id, player_id=player_id).first_or_404()
new_status = request.form.get('status')
if new_status in ['registered', 'attended', 'no_show']:
registration.status = new_status
db.session.commit()
flash('Registration status updated.', 'success')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
@tryouts_bp.route('/<int:tryout_id>/register_player', methods=['POST'])
@login_required
def register_player(tryout_id):
if not can_manage():
flash('Permission denied.', 'danger')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
tryout = Tryout.query.get_or_404(tryout_id)
player_id = request.form.get('player_id')
if not player_id:
flash('Please select a player.', 'danger')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
player = User.query.get_or_404(int(player_id))
if player.role != 'player':
flash('Can only register players.', 'danger')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
existing = TryoutRegistration.query.filter_by(tryout_id=tryout_id, player_id=player.id).first()
if existing:
flash(f'{player.full_name} is already registered for this tryout.', 'info')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
if tryout.max_players:
count = TryoutRegistration.query.filter_by(tryout_id=tryout_id).count()
if count >= tryout.max_players:
flash('This tryout is full.', 'danger')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
registration = TryoutRegistration(tryout_id=tryout_id, player_id=player.id)
db.session.add(registration)
db.session.commit()
flash(f'{player.full_name} registered for tryout!', 'success')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
@tryouts_bp.route('/<int:tryout_id>/team/create', methods=['POST'])
@login_required
def create_team(tryout_id):
if not can_manage():
flash('Permission denied.', 'danger')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
team_name = request.form.get('team_name')
if team_name:
team = Team(tryout_id=tryout_id, name=team_name, created_by=current_user.id)
db.session.add(team)
db.session.commit()
flash(f'Team "{team_name}" created!', 'success')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
@tryouts_bp.route('/<int:tryout_id>/team/<int:team_id>/add', methods=['POST'])
@login_required
def add_to_team(tryout_id, team_id):
if not can_manage():
flash('Permission denied.', 'danger')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
player_id = request.form.get('player_id')
position = request.form.get('position', '')
team = Team.query.get_or_404(team_id)
existing = TeamMember.query.filter_by(team_id=team_id, player_id=player_id).first()
if existing:
flash('Player is already on this team.', 'info')
else:
member = TeamMember(team_id=team_id, player_id=int(player_id), position=position)
db.session.add(member)
db.session.commit()
flash('Player added to team!', 'success')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
+164
View File
@@ -0,0 +1,164 @@
from flask import Blueprint, render_template, redirect, url_for, flash, request
from flask_login import login_required, current_user
from extensions import db, hash_password
from models import User, ROLES, ESPORT_GAMES
users_bp = Blueprint('users', __name__, url_prefix='/users')
@users_bp.route('')
@login_required
def list_users():
if current_user.role != 'president':
flash('Only the president can manage users.', 'danger')
return redirect(url_for('main.dashboard'))
users = User.query.order_by(User.role, User.full_name).all()
return render_template('pages/users.html', users=users, roles=ROLES)
@users_bp.route('/<int:user_id>/edit', methods=['GET', 'POST'])
@login_required
def edit_user(user_id):
if current_user.role != 'president':
flash('Only the president can edit users.', 'danger')
return redirect(url_for('main.dashboard'))
user = User.query.get_or_404(user_id)
if request.method == 'POST':
full_name = request.form.get('full_name')
email = request.form.get('email')
phone = request.form.get('phone')
role = request.form.get('role')
is_active = request.form.get('is_active_account') == 'on'
if role not in ROLES:
flash('Invalid role selected.', 'danger')
return render_template('pages/edit_user.html', user=user, roles=ROLES, esport_games=ESPORT_GAMES)
# E-Sports fields
selected_games = request.form.getlist('games')
trn_username = request.form.get('trn_username', '').strip()
discord_username = request.form.get('discord_username', '').strip()
league_os_profile = request.form.get('league_os_profile', '').strip()
user.full_name = full_name
user.email = email
user.phone = phone
user.role = role
user.is_active_account = is_active
user.games = ','.join(selected_games) if selected_games else None
user.trn_username = trn_username or None
user.discord_username = discord_username or None
user.league_os_profile = league_os_profile or None
password = request.form.get('password')
if password:
user.password_hash = hash_password(password)
db.session.commit()
flash(f'User {user.full_name} updated successfully!', 'success')
return redirect(url_for('users.list_users'))
return render_template('pages/edit_user.html', user=user, roles=ROLES, esport_games=ESPORT_GAMES)
@users_bp.route('/<int:user_id>/delete', methods=['POST'])
@login_required
def delete_user(user_id):
if current_user.role != 'president':
flash('Only the president can delete users.', 'danger')
return redirect(url_for('main.dashboard'))
if current_user.id == user_id:
flash('You cannot delete your own account.', 'danger')
return redirect(url_for('users.list_users'))
user = User.query.get_or_404(user_id)
db.session.delete(user)
db.session.commit()
flash(f'User {user.full_name} has been removed.', 'success')
return redirect(url_for('users.list_users'))
@users_bp.route('/create', methods=['GET', 'POST'])
@login_required
def create_user():
if current_user.role != 'president':
flash('Only the president can create users.', 'danger')
return redirect(url_for('main.dashboard'))
if request.method == 'POST':
username = request.form.get('username')
email = request.form.get('email')
password = request.form.get('password')
full_name = request.form.get('full_name')
phone = request.form.get('phone')
role = request.form.get('role')
if role not in ROLES:
flash('Invalid role selected.', 'danger')
return render_template('pages/create_user.html', roles=ROLES)
if User.query.filter_by(username=username).first():
flash('Username already exists.', 'danger')
return render_template('pages/create_user.html', roles=ROLES)
if User.query.filter_by(email=email).first():
flash('Email already registered.', 'danger')
return render_template('pages/create_user.html', roles=ROLES)
hashed_password = hash_password(password)
user = User(
username=username,
password_hash=hashed_password,
role=role,
full_name=full_name,
email=email,
phone=phone
)
db.session.add(user)
db.session.commit()
flash(f'User {full_name} created as {role}!', 'success')
return redirect(url_for('users.list_users'))
return render_template('pages/create_user.html', roles=ROLES)
@users_bp.route('/profile')
@login_required
def profile():
return render_template('pages/profile.html', user=current_user)
@users_bp.route('/profile/edit', methods=['GET', 'POST'])
@login_required
def edit_profile():
if request.method == 'POST':
full_name = request.form.get('full_name')
email = request.form.get('email')
phone = request.form.get('phone')
# E-Sports fields
selected_games = request.form.getlist('games')
trn_username = request.form.get('trn_username', '').strip()
discord_username = request.form.get('discord_username', '').strip()
league_os_profile = request.form.get('league_os_profile', '').strip()
# Check email uniqueness if changed
if email != current_user.email and User.query.filter_by(email=email).first():
flash('Email already in use.', 'danger')
return render_template('pages/edit_profile.html', user=current_user, esport_games=ESPORT_GAMES)
current_user.full_name = full_name
current_user.email = email
current_user.phone = phone
current_user.games = ','.join(selected_games) if selected_games else None
current_user.trn_username = trn_username or None
current_user.discord_username = discord_username or None
current_user.league_os_profile = league_os_profile or None
password = request.form.get('password')
if password:
current_user.password_hash = hash_password(password)
db.session.commit()
flash('Profile updated successfully!', 'success')
return redirect(url_for('users.profile'))
return render_template('pages/edit_profile.html', user=current_user, esport_games=ESPORT_GAMES)
+254
View File
@@ -0,0 +1,254 @@
from extensions import db, hash_password
from models import User, Tryout, TryoutRegistration, Evaluation, Team, TeamMember, OrgTeam
from datetime import datetime, timedelta
import random
from sqlalchemy import text
def seed_database():
# Clear existing data
db.session.execute(text('DELETE FROM team_members'))
db.session.execute(text('DELETE FROM teams'))
db.session.execute(text('DELETE FROM evaluations'))
db.session.execute(text('DELETE FROM tryout_registrations'))
db.session.execute(text('DELETE FROM tryouts'))
db.session.execute(text('DELETE FROM org_teams'))
db.session.execute(text('DELETE FROM users'))
db.session.commit()
# Create users with different roles
users_data = [
{'username': 'president', 'password': 'password', 'role': 'president', 'full_name': 'Sarah Johnson', 'email': '[email protected]', 'phone': '555-0101'},
{'username': 'manager1', 'password': 'password', 'role': 'manager', 'full_name': 'Mike Williams', 'email': '[email protected]', 'phone': '555-0102'},
{'username': 'manager2', 'password': 'password', 'role': 'manager', 'full_name': 'Emily Davis', 'email': '[email protected]', 'phone': '555-0103'},
{'username': 'coach1', 'password': 'password', 'role': 'coach', 'full_name': 'Coach Thompson', 'email': '[email protected]', 'phone': '555-0104'},
{'username': 'coach2', 'password': 'password', 'role': 'coach', 'full_name': 'Coach Martinez', 'email': '[email protected]', 'phone': '555-0105'},
{'username': 'coach3', 'password': 'password', 'role': 'coach', 'full_name': 'Coach Anderson', 'email': '[email protected]', 'phone': '555-0106'},
{'username': 'scout1', 'password': 'password', 'role': 'scout', 'full_name': 'Alex Rivera', 'email': '[email protected]', 'phone': '555-0107'},
]
# Create players with E-Sports profile data
player_data = [
{'username': 'jplayer1', 'full_name': 'James Wilson', 'email': '[email protected]', 'games': 'Valorant,Counter-Strike 2', 'trn': 'jameswilson_tr', 'discord': 'JamesW#7291', 'league_os': 'https://leagueos.gg/player/jameswilson'},
{'username': 'jplayer2', 'full_name': 'Emma Garcia', 'email': '[email protected]', 'games': 'League of Legends,Valorant', 'trn': 'emmagarcia_gg', 'discord': 'EmmaG#4452', 'league_os': 'https://leagueos.gg/player/emmagarcia'},
{'username': 'jplayer3', 'full_name': 'Liam Brown', 'email': '[email protected]', 'games': 'Apex Legends,Fortnite', 'trn': 'liambrown_fn', 'discord': 'LiamB#8103', 'league_os': 'https://leagueos.gg/player/liambrown'},
{'username': 'jplayer4', 'full_name': 'Sophia Lee', 'email': '[email protected]', 'games': 'Overwatch 2,Valorant', 'trn': 'sophialee_ow', 'discord': 'SophiaL#3327', 'league_os': 'https://leagueos.gg/player/sophialee'},
{'username': 'jplayer5', 'full_name': 'Noah Taylor', 'email': '[email protected]', 'games': 'Counter-Strike 2,Rainbow Six Siege', 'trn': 'noahtaylor_r6', 'discord': 'NoahT#6614', 'league_os': 'https://leagueos.gg/player/noahtaylor'},
{'username': 'jplayer6', 'full_name': 'Olivia Martin', 'email': '[email protected]', 'games': 'Rocket League,Fortnite', 'trn': 'oliviamartin_rl', 'discord': 'OliviaM#2298', 'league_os': 'https://leagueos.gg/player/oliviamartin'},
{'username': 'jplayer7', 'full_name': 'Ethan Clark', 'email': '[email protected]', 'games': 'Valorant,Apex Legends', 'trn': 'ethanclark_val', 'discord': 'EthanC#7743', 'league_os': 'https://leagueos.gg/player/ethanclark'},
{'username': 'jplayer8', 'full_name': 'Ava White', 'email': '[email protected]', 'games': 'League of Legends,Counter-Strike 2', 'trn': 'avawhite_lol', 'discord': 'AvaW#5561', 'league_os': 'https://leagueos.gg/player/avawhite'},
{'username': 'jplayer9', 'full_name': 'Mason Hall', 'email': '[email protected]', 'games': 'Call of Duty,Rocket League', 'trn': 'masonhall_cod', 'discord': 'MasonH#1189', 'league_os': 'https://leagueos.gg/player/masonhall'},
{'username': 'jplayer10', 'full_name': 'Isabella Adams', 'email': '[email protected]', 'games': 'Overwatch 2,Dota 2', 'trn': 'isabellaadams_ow', 'discord': 'IsabellaA#4437', 'league_os': 'https://leagueos.gg/player/isabellaadams'},
]
for i, data in enumerate(player_data, start=10):
users_data.append({
'username': data['username'], 'password': 'password', 'role': 'player',
'full_name': data['full_name'], 'email': data['email'], 'phone': f'555-01{i:02d}',
'games': data['games'], 'trn_username': data['trn'],
'discord_username': data['discord'], 'league_os_profile': data['league_os']
})
users = []
for data in users_data:
hashed_pw = hash_password(data['password'])
user = User(
username=data['username'],
password_hash=hashed_pw,
role=data['role'],
full_name=data['full_name'],
email=data['email'],
phone=data.get('phone', ''),
games=data.get('games'),
trn_username=data.get('trn_username'),
discord_username=data.get('discord_username'),
league_os_profile=data.get('league_os_profile')
)
db.session.add(user)
users.append(user)
db.session.commit()
print(f"✓ Created {len(users)} users")
# Map users for easy access
user_map = {u.username: u for u in users}
president = user_map['president']
manager1 = user_map['manager1']
manager2 = user_map['manager2']
coaches = [user_map['coach1'], user_map['coach2'], user_map['coach3']]
scout = user_map['scout1']
players = [user_map[f'jplayer{i}'] for i in range(1, 11)]
# Create Organization Teams (OrgTeams)
org_teams_data = [
{'name': 'Varsity', 'coach': coaches[0], 'creator': president},
{'name': 'Junior Varsity', 'coach': coaches[1], 'creator': president},
{'name': 'U14 Development', 'coach': coaches[2], 'creator': president},
{'name': 'Select Team', 'coach': None, 'creator': president},
]
org_teams = []
for data in org_teams_data:
ot = OrgTeam(
name=data['name'],
coach_id=data['coach'].id if data['coach'] else None,
created_by=data['creator'].id
)
db.session.add(ot)
org_teams.append(ot)
db.session.commit()
print(f"✓ Created {len(org_teams)} organization teams")
# Create tryouts
tryouts_data = [
{'title': 'Spring Season Tryouts', 'date': datetime.utcnow() - timedelta(days=5), 'location': 'Main Stadium', 'description': 'Tryouts for the spring competitive season. All positions welcome.', 'status': 'in_progress', 'creator': manager1, 'target_team': org_teams[0]},
{'title': 'Fall Select Team Trials', 'date': datetime.utcnow() + timedelta(days=20), 'location': 'Training Center', 'description': 'Trials for the fall select team. High skill level required.', 'status': 'upcoming', 'creator': manager1, 'target_team': org_teams[3]},
{'title': 'Youth Development Camp', 'date': datetime.utcnow() - timedelta(days=20), 'location': 'Community Field', 'description': 'Development camp for younger players to showcase their skills.', 'status': 'completed', 'creator': manager2, 'target_team': org_teams[2]},
{'title': 'Winter Indoor Showcase', 'date': datetime.utcnow() - timedelta(days=2), 'location': 'Indoor Arena', 'description': 'Indoor showcase event for scouting and team selection.', 'status': 'in_progress', 'creator': manager2, 'target_team': org_teams[1]},
]
tryouts = []
for data in tryouts_data:
t = Tryout(
title=data['title'],
date=data['date'].date(),
location=data['location'],
description=data['description'],
status=data['status'],
max_players=15,
created_by=data['creator'].id,
target_org_team_id=data['target_team'].id if data['target_team'] else None
)
db.session.add(t)
tryouts.append(t)
db.session.commit()
print(f"✓ Created {len(tryouts)} tryouts")
# Register players for tryouts
registrations_data = [
(tryouts[0], players[:8]),
(tryouts[1], players),
(tryouts[2], players[:6]),
(tryouts[3], players[2:9]),
]
regs = []
for tryout, player_list in registrations_data:
for player in player_list:
reg = TryoutRegistration(
tryout_id=tryout.id,
player_id=player.id,
status=random.choice(['registered', 'attended', 'attended'])
)
db.session.add(reg)
regs.append(reg)
db.session.commit()
print(f"✓ Created {len(regs)} registrations")
# Create evaluations (for in_progress and completed tryouts)
eval_data = []
# Spring tryout - some evaluations
for player in players[:8]:
for coach in coaches:
if random.random() > 0.3:
speed = random.randint(4, 10)
agility = random.randint(4, 10)
technique = random.randint(3, 10)
teamwork = random.randint(5, 10)
attitude = random.randint(5, 10)
overall = round((speed + agility + technique + teamwork + attitude) / 5, 1)
positions = ['Forward', 'Midfield', 'Defense', 'Goalie', 'Wing', 'Center']
eval_entry = Evaluation(
tryout_id=tryouts[0].id,
player_id=player.id,
evaluator_id=coach.id,
speed_score=speed,
agility_score=agility,
technique_score=technique,
teamwork_score=teamwork,
attitude_score=attitude,
overall_score=overall,
comments=f"{'Great' if overall > 7 else 'Good'} performance. {'Shows promise.' if overall > 6 else 'Needs improvement in some areas.'}",
position_recommendation=random.choice(positions)
)
db.session.add(eval_entry)
eval_data.append(eval_entry)
# Completed tryout - full evaluations
for player in players[:6]:
for coach in coaches[:2]:
speed = random.randint(3, 10)
agility = random.randint(3, 10)
technique = random.randint(3, 10)
teamwork = random.randint(4, 10)
attitude = random.randint(4, 10)
overall = round((speed + agility + technique + teamwork + attitude) / 5, 1)
positions = ['Forward', 'Midfield', 'Defense', 'Goalie', 'Wing', 'Center']
eval_entry = Evaluation(
tryout_id=tryouts[2].id,
player_id=player.id,
evaluator_id=coach.id,
speed_score=speed,
agility_score=agility,
technique_score=technique,
teamwork_score=teamwork,
attitude_score=attitude,
overall_score=overall,
comments=f"{'Excellent' if overall > 8 else 'Solid'} display of skills during the camp.",
position_recommendation=random.choice(positions)
)
db.session.add(eval_entry)
eval_data.append(eval_entry)
db.session.commit()
print(f"✓ Created {len(eval_data)} evaluations")
# Create teams for the completed tryout (tryout-specific teams)
team1 = Team(tryout_id=tryouts[2].id, name='Alpha Team', created_by=president.id)
team2 = Team(tryout_id=tryouts[2].id, name='Bravo Team', created_by=president.id)
db.session.add(team1)
db.session.add(team2)
db.session.commit()
# Assign players to teams
team_members_data = [
(team1.id, players[0].id, 'Forward'),
(team1.id, players[1].id, 'Midfield'),
(team1.id, players[2].id, 'Defense'),
(team1.id, players[3].id, 'Goalie'),
(team2.id, players[4].id, 'Midfield'),
(team2.id, players[5].id, 'Forward'),
]
for team_id, player_id, position in team_members_data:
tm = TeamMember(team_id=team_id, player_id=player_id, position=position)
db.session.add(tm)
db.session.commit()
print("✓ Created 2 tryout-specific teams with player assignments")
# Assign players to organization teams
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]),
]
for org_team, player in org_team_assignments:
player.team_id = org_team.id
db.session.commit()
print("✓ Assigned players to organization teams")
print("\n🎉 Database seeded successfully!")
print("\n=== Login Credentials ===")
print("President: username='president', 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)")
print("Coach: username='coach3', password='password' (assigned to U14 Development)")
print("Player: username='jplayer1', password='password'")
print("Scout: username='scout1', password='password'")
if __name__ == '__main__':
seed_database()
+913
View File
@@ -0,0 +1,913 @@
:root {
--primary: #4f46e5;
--primary-dark: #4338ca;
--primary-light: #eef2ff;
--success: #10b981;
--success-light: #ecfdf5;
--warning: #f59e0b;
--warning-light: #fffbeb;
--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;
--sidebar-width: 260px;
--sidebar-collapsed: 0px;
--radius: 12px;
--radius-sm: 8px;
--shadow: 0 1px 3px rgba(0,0,0,0.1), 0 1px 2px rgba(0,0,0,0.06);
--shadow-md: 0 4px 6px rgba(0,0,0,0.07), 0 2px 4px rgba(0,0,0,0.06);
--shadow-lg: 0 10px 15px rgba(0,0,0,0.1), 0 4px 6px rgba(0,0,0,0.05);
--transition: all 0.2s ease;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
background: var(--gray-50);
color: var(--gray-800);
line-height: 1.6;
min-height: 100vh;
}
a { color: var(--primary); text-decoration: none; }
a:hover { color: var(--primary-dark); }
/* Sidebar */
.sidebar {
position: fixed;
left: 0;
top: 0;
width: var(--sidebar-width);
height: 100vh;
background: var(--dark);
color: white;
z-index: 1000;
transition: var(--transition);
overflow-y: auto;
}
.sidebar-header {
padding: 20px;
border-bottom: 1px solid rgba(255,255,255,0.1);
}
.logo {
display: flex;
align-items: center;
gap: 10px;
font-size: 1.4rem;
font-weight: 700;
margin-bottom: 20px;
}
.logo i { color: var(--primary); font-size: 1.6rem; }
.user-badge {
display: flex;
align-items: center;
gap: 12px;
padding: 10px;
background: rgba(255,255,255,0.05);
border-radius: var(--radius-sm);
}
.user-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: var(--primary);
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 0.9rem;
flex-shrink: 0;
}
.avatar-sm { width: 32px; height: 32px; font-size: 0.75rem; }
.avatar-lg { width: 48px; height: 48px; font-size: 1rem; }
.avatar-xl { width: 64px; height: 64px; font-size: 1.3rem; }
.user-info {
display: flex;
flex-direction: column;
gap: 2px;
overflow: hidden;
}
.user-name { font-weight: 600; font-size: 0.9rem; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.user-role { font-size: 0.75rem; padding: 2px 8px; border-radius: 20px; display: inline-block; width: fit-content; }
.nav-links {
list-style: none;
padding: 10px 0;
}
.nav-links li a {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 20px;
color: var(--gray-400);
transition: var(--transition);
font-size: 0.9rem;
}
.nav-links li a:hover, .nav-links li a.active {
background: rgba(255,255,255,0.08);
color: white;
}
.nav-links li a.active {
border-left: 3px solid var(--primary);
padding-left: 17px;
}
.nav-links li a i { width: 20px; text-align: center; font-size: 1.1rem; }
.nav-divider {
height: 1px;
background: rgba(255,255,255,0.1);
margin: 10px 20px;
}
.logout-link { color: var(--danger) !important; }
.logout-link:hover { background: rgba(239,68,68,0.1) !important; }
/* Main Content */
.main-content {
margin-left: var(--sidebar-width);
transition: var(--transition);
min-height: 100vh;
}
.top-bar {
display: flex;
align-items: center;
gap: 16px;
padding: 16px 30px;
background: white;
border-bottom: 1px solid var(--gray-200);
position: sticky;
top: 0;
z-index: 100;
}
.sidebar-toggle {
background: none;
border: none;
font-size: 1.3rem;
color: var(--gray-500);
cursor: pointer;
padding: 4px 8px;
border-radius: var(--radius-sm);
display: none;
}
.sidebar-toggle:hover { background: var(--gray-100); }
.page-header h1 {
font-size: 1.3rem;
font-weight: 700;
color: var(--gray-900);
}
.breadcrumb { font-size: 0.8rem; color: var(--gray-500); }
.breadcrumb a { color: var(--primary); }
.content { padding: 24px 30px; }
/* Flash Messages */
.flash-messages {
padding: 0 30px;
margin-top: 8px;
}
.alert {
padding: 12px 16px;
border-radius: var(--radius-sm);
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
font-size: 0.9rem;
animation: slideIn 0.3s ease;
}
@keyframes slideIn {
from { transform: translateY(-10px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
.alert-success { background: var(--success-light); color: #065f46; border: 1px solid #a7f3d0; }
.alert-danger { background: var(--danger-light); color: #991b1b; border: 1px solid #fecaca; }
.alert-warning { background: var(--warning-light); color: #92400e; border: 1px solid #fde68a; }
.alert-info { background: var(--info-light); color: #1e40af; border: 1px solid #bfdbfe; }
.alert-close {
background: none;
border: none;
font-size: 1.3rem;
cursor: pointer;
color: inherit;
opacity: 0.6;
padding: 0 4px;
}
.alert-close:hover { opacity: 1; }
/* Cards */
.card {
background: white;
border-radius: var(--radius);
box-shadow: var(--shadow);
overflow: hidden;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid var(--gray-200);
}
.card-header h3 {
font-size: 1rem;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
}
.card-body { padding: 20px; }
/* Stats Grid */
.stats-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
margin-bottom: 24px;
}
.stat-card {
background: white;
border-radius: var(--radius);
padding: 20px;
display: flex;
align-items: center;
gap: 16px;
box-shadow: var(--shadow);
transition: var(--transition);
}
.stat-card:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }
.stat-card-sm { padding: 14px; }
.stat-card-sm .stat-info h3 { font-size: 0.9rem; }
.stat-card-sm .stat-info p { font-size: 0.75rem; }
.stat-icon {
width: 48px;
height: 48px;
border-radius: var(--radius-sm);
display: flex;
align-items: center;
justify-content: center;
font-size: 1.3rem;
flex-shrink: 0;
}
.stat-info h3 { font-size: 1.5rem; font-weight: 700; color: var(--gray-900); }
.stat-info p { font-size: 0.85rem; color: var(--gray-500); margin-top: 2px; }
.bg-primary { background: var(--primary-light); color: var(--primary); }
.bg-success { background: var(--success-light); color: var(--success); }
.bg-warning { background: var(--warning-light); color: var(--warning); }
.bg-info { background: var(--info-light); color: var(--info); }
.bg-danger { background: var(--danger-light); color: var(--danger); }
.bg-secondary { background: var(--secondary-light); color: var(--secondary); }
/* Dashboard Grid */
.dashboard-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
}
@media (max-width: 900px) {
.dashboard-grid { grid-template-columns: 1fr; }
}
/* Tables */
.table-container { overflow-x: auto; }
.table {
width: 100%;
border-collapse: collapse;
font-size: 0.9rem;
}
.table th {
text-align: left;
padding: 10px 12px;
font-weight: 600;
color: var(--gray-500);
font-size: 0.8rem;
text-transform: uppercase;
letter-spacing: 0.05em;
border-bottom: 2px solid var(--gray-200);
}
.table td {
padding: 10px 12px;
border-bottom: 1px solid var(--gray-100);
vertical-align: middle;
}
.table tbody tr:hover { background: var(--gray-50); }
.cell-title { font-weight: 600; }
/* Badges */
.badge {
display: inline-block;
padding: 3px 10px;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.03em;
}
.badge-lg { padding: 5px 14px; font-size: 0.85rem; }
.badge-upcoming { background: var(--info-light); color: var(--info); }
.badge-in_progress { background: var(--warning-light); color: var(--warning); }
.badge-completed { background: var(--success-light); color: var(--success); }
.badge-president { background: #eef2ff; color: #4338ca; }
.badge-manager { background: #ecfdf5; color: #059669; }
.badge-coach { background: #fffbeb; color: #d97706; }
.badge-player { background: #f3f4f6; color: #4b5563; }
.badge-scout { background: #fef2f2; color: #dc2626; }
.badge-success { background: var(--success-light); color: var(--success); }
.badge-warning { background: var(--warning-light); color: var(--warning); }
.badge-danger { background: var(--danger-light); color: var(--danger); }
.badge-info { background: var(--info-light); color: var(--info); }
.badge-registered { background: var(--info-light); color: var(--info); }
.badge-attended { background: var(--success-light); color: var(--success); }
.badge-no_show { background: var(--danger-light); color: var(--danger); }
.score {
font-weight: 700;
color: var(--primary);
font-size: 1.1rem;
}
/* Buttons */
.btn {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 8px 16px;
border-radius: var(--radius-sm);
font-size: 0.9rem;
font-weight: 500;
border: none;
cursor: pointer;
transition: var(--transition);
text-decoration: none;
}
.btn-primary { background: var(--primary); color: white; }
.btn-primary:hover { background: var(--primary-dark); color: white; }
.btn-secondary { background: var(--gray-100); color: var(--gray-700); }
.btn-secondary:hover { background: var(--gray-200); }
.btn-success { background: var(--success); color: white; }
.btn-success:hover { background: #059669; color: white; }
.btn-danger { background: var(--danger); color: white; }
.btn-danger:hover { background: #dc2626; color: white; }
.btn-outline {
background: transparent;
border: 1px solid var(--gray-300);
color: var(--gray-700);
}
.btn-outline:hover { background: var(--gray-50); border-color: var(--gray-400); }
.btn-sm { padding: 5px 10px; font-size: 0.8rem; }
.btn-lg { padding: 12px 24px; font-size: 1rem; }
.btn-block { width: 100%; justify-content: center; }
/* Forms */
.form { max-width: 800px; }
.form-row {
display: flex;
gap: 16px;
margin-bottom: 0;
}
.form-group {
margin-bottom: 16px;
flex: 1;
}
.form-group label {
display: block;
font-size: 0.85rem;
font-weight: 600;
color: var(--gray-700);
margin-bottom: 6px;
}
.form-group input[type="text"],
.form-group input[type="email"],
.form-group input[type="password"],
.form-group input[type="tel"],
.form-group input[type="number"],
.form-group input[type="date"],
.form-group select,
.form-group textarea {
width: 100%;
padding: 10px 12px;
border: 1px solid var(--gray-300);
border-radius: var(--radius-sm);
font-size: 0.9rem;
transition: var(--transition);
background: white;
font-family: inherit;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(79,70,229,0.1);
}
.form-group textarea { resize: vertical; min-height: 80px; }
.form-select { padding: 8px 12px; border: 1px solid var(--gray-300); border-radius: var(--radius-sm); font-size: 0.9rem; }
.form-select-sm { padding: 4px 8px; font-size: 0.8rem; }
.form-input { padding: 8px 12px; border: 1px solid var(--gray-300); border-radius: var(--radius-sm); font-size: 0.9rem; }
.form-actions {
display: flex;
gap: 10px;
margin-top: 20px;
padding-top: 16px;
border-top: 1px solid var(--gray-200);
}
.form-inline {
display: flex;
align-items: center;
gap: 8px;
}
.inline-form { display: inline; }
.col-12 { flex: 0 0 100%; }
.col-6 { flex: 0 0 calc(50% - 8px); }
.col-4 { flex: 0 0 calc(33.333% - 11px); }
.col-3 { flex: 0 0 calc(25% - 12px); }
/* Score Input */
.score-input {
display: flex;
align-items: center;
gap: 12px;
}
.score-input input[type="range"] {
flex: 1;
height: 6px;
-webkit-appearance: none;
appearance: none;
background: var(--gray-200);
border-radius: 3px;
outline: none;
}
.score-input input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--primary);
cursor: pointer;
}
.range-value {
font-size: 1.2rem;
font-weight: 700;
color: var(--primary);
min-width: 30px;
text-align: center;
}
/* Auth */
.auth-wrapper {
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
padding: 20px;
}
.auth-wrapper .flash-messages {
width: 100%;
max-width: 420px;
padding: 0;
margin-bottom: 12px;
}
.auth-container {
background: white;
border-radius: var(--radius);
box-shadow: var(--shadow-lg);
padding: 40px;
width: 100%;
max-width: 520px;
}
.auth-header {
text-align: center;
margin-bottom: 30px;
}
.auth-header i {
font-size: 3rem;
color: var(--primary);
margin-bottom: 12px;
}
.auth-header h2 {
font-size: 1.6rem;
font-weight: 700;
color: var(--gray-900);
}
.auth-header p {
color: var(--gray-500);
font-size: 0.9rem;
margin-top: 4px;
}
.auth-form .form-group {
margin-bottom: 18px;
}
.auth-form .form-group label {
font-size: 0.85rem;
font-weight: 600;
color: var(--gray-700);
margin-bottom: 6px;
display: flex;
align-items: center;
gap: 6px;
}
.auth-form .form-group label i {
color: var(--gray-400);
width: 16px;
}
.auth-form .form-group input {
width: 100%;
padding: 12px 14px;
border: 1px solid var(--gray-300);
border-radius: var(--radius-sm);
font-size: 0.95rem;
transition: var(--transition);
}
.auth-form .form-group input:focus {
border-color: var(--primary);
box-shadow: 0 0 0 3px rgba(79,70,229,0.1);
outline: none;
}
.auth-link {
text-align: center;
margin-top: 16px;
font-size: 0.9rem;
color: var(--gray-500);
}
/* Detail Grid */
.detail-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
.detail-item { display: flex; flex-direction: column; gap: 4px; }
.detail-item.full-width { grid-column: 1 / -1; }
.detail-label { font-size: 0.8rem; color: var(--gray-500); font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; }
.detail-value { font-size: 0.95rem; color: var(--gray-800); }
/* User Mini */
.user-mini {
display: flex;
align-items: center;
gap: 8px;
}
/* Profile */
.profile-header {
display: flex;
align-items: center;
gap: 20px;
}
.profile-info h2 { font-size: 1.3rem; margin-bottom: 4px; }
.profile-info p { font-size: 0.85rem; }
.profile-stats {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 16px;
}
.stat-item {
text-align: center;
padding: 16px;
background: var(--gray-50);
border-radius: var(--radius-sm);
}
.stat-item h4 { font-size: 1.5rem; font-weight: 700; color: var(--primary); }
.stat-item p { font-size: 0.8rem; color: var(--gray-500); }
/* Team */
.team-card {
background: var(--gray-50);
border-radius: var(--radius-sm);
padding: 16px;
}
.team-name {
font-size: 1rem;
font-weight: 600;
margin-bottom: 10px;
color: var(--gray-800);
}
.team-members {
list-style: none;
}
.team-members li {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 0;
font-size: 0.9rem;
}
.position-tag {
background: var(--primary-light);
color: var(--primary);
padding: 2px 8px;
border-radius: 12px;
font-size: 0.75rem;
font-weight: 600;
}
/* Eval Player Info */
.eval-player-info {
display: flex;
align-items: center;
gap: 16px;
}
/* Empty State */
.empty-state {
text-align: center;
padding: 40px 20px;
}
.empty-state i {
font-size: 3rem;
color: var(--gray-300);
margin-bottom: 12px;
}
.empty-state h3 { font-size: 1.1rem; color: var(--gray-600); margin-bottom: 8px; }
.empty-state p { color: var(--gray-400); font-size: 0.9rem; margin-bottom: 16px; }
/* Modal */
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 2000;
display: flex;
align-items: center;
justify-content: center;
}
.modal-backdrop {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 2001;
}
.modal-content {
background: white;
border-radius: var(--radius);
box-shadow: var(--shadow-lg);
width: 100%;
max-width: 500px;
z-index: 2002;
position: relative;
max-height: 90vh;
overflow-y: auto;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px 20px;
border-bottom: 1px solid var(--gray-200);
}
.modal-header h3 {
font-size: 1.1rem;
font-weight: 600;
}
.modal-close {
background: none;
border: none;
font-size: 1.5rem;
color: var(--gray-400);
cursor: pointer;
padding: 0 4px;
}
.modal-close:hover { color: var(--gray-700); }
.modal-body {
padding: 20px;
}
/* E-Sports Styles */
.checkbox-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
gap: 8px;
margin-top: 4px;
}
.checkbox-label {
display: flex;
align-items: center;
gap: 6px;
padding: 6px 10px;
border: 1px solid var(--gray-200);
border-radius: var(--radius-sm);
cursor: pointer;
transition: var(--transition);
font-size: 0.85rem;
font-weight: 500;
color: var(--gray-700);
}
.checkbox-label:hover {
border-color: var(--primary);
background: var(--primary-light);
}
.checkbox-label input[type="checkbox"] {
accent-color: var(--primary);
width: 16px;
height: 16px;
cursor: pointer;
}
.section-divider {
border: none;
border-top: 1px solid var(--gray-200);
margin: 20px 0;
}
.section-title {
font-size: 1rem;
font-weight: 600;
color: var(--gray-800);
margin-bottom: 12px;
display: flex;
align-items: center;
gap: 8px;
}
.section-title i {
color: var(--primary);
}
.badge-esport {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 4px 12px;
border-radius: 20px;
font-size: 0.75rem;
font-weight: 600;
display: inline-block;
margin: 2px 3px;
}
.trn-link {
color: var(--primary);
font-weight: 600;
display: inline-flex;
align-items: center;
gap: 4px;
transition: var(--transition);
}
.trn-link:hover {
color: var(--primary-dark);
text-decoration: underline;
}
.form-text {
font-size: 0.8rem;
color: var(--gray-500);
margin-top: 4px;
display: block;
}
.small {
font-size: 0.85rem;
}
.header-actions {
display: flex;
align-items: center;
gap: 8px;
margin-left: auto;
}
/* Utilities */
.hidden { display: none !important; }
.text-center { text-align: center; }
.text-muted { color: var(--gray-500); }
.mt-2 { margin-top: 8px; }
.mt-4 { margin-top: 16px; }
.mb-3 { margin-bottom: 12px; }
.mb-4 { margin-bottom: 16px; }
.mr-2 { margin-right: 8px; }
.mx-2 { margin-left: 8px; margin-right: 8px; }
/* Responsive */
@media (max-width: 768px) {
.sidebar {
width: var(--sidebar-collapsed);
transform: translateX(-100%);
}
.sidebar.open {
width: var(--sidebar-width);
transform: translateX(0);
}
.main-content {
margin-left: 0;
}
.sidebar-toggle { display: block; }
.stats-grid { grid-template-columns: repeat(2, 1fr); }
.form-row { flex-direction: column; }
.col-6, .col-4, .col-3 { flex: 0 0 100%; }
.profile-stats { grid-template-columns: 1fr; }
.detail-grid { grid-template-columns: 1fr; }
.content { padding: 16px; }
.top-bar { padding: 12px 16px; }
.flash-messages { padding: 0 16px; }
}
+43
View File
@@ -0,0 +1,43 @@
// Sidebar toggle for mobile
function toggleSidebar() {
const sidebar = document.getElementById('sidebar');
sidebar.classList.toggle('open');
}
// Close sidebar when clicking outside on mobile
document.addEventListener('click', function(event) {
const sidebar = document.getElementById('sidebar');
const toggle = document.getElementById('sidebarToggle');
if (window.innerWidth <= 768) {
if (!sidebar.contains(event.target) && !toggle.contains(event.target)) {
sidebar.classList.remove('open');
}
}
});
// Auto-dismiss flash messages
document.addEventListener('DOMContentLoaded', function() {
const alerts = document.querySelectorAll('.alert-dismissible');
alerts.forEach(function(alert) {
setTimeout(function() {
if (alert.parentElement) {
alert.style.opacity = '0';
alert.style.transition = 'opacity 0.3s ease';
setTimeout(function() {
if (alert.parentElement) {
alert.remove();
}
}, 300);
}
}, 5000);
});
});
// Confirm delete actions
document.querySelectorAll('[data-confirm]').forEach(function(el) {
el.addEventListener('click', function(e) {
if (!confirm(this.getAttribute('data-confirm'))) {
e.preventDefault();
}
});
});
+134
View File
@@ -0,0 +1,134 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Team Tryout Management{% endblock %}</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>🏆</text></svg>">
</head>
<body>
{% if current_user.is_authenticated %}
<nav class="sidebar" id="sidebar">
<div class="sidebar-header">
<div class="logo">
<i class="fas fa-trophy"></i>
<span>TryoutPro</span>
</div>
<div class="user-badge">
<div class="user-avatar">
{{ current_user.full_name[:2] | upper }}
</div>
<div class="user-info">
<span class="user-name">{{ current_user.full_name }}</span>
<span class="user-role role-{{ current_user.role }}">{{ current_user.role | capitalize }}</span>
</div>
</div>
</div>
<ul class="nav-links">
<li>
<a href="{{ url_for('main.dashboard') }}" class="{% if request.endpoint and 'dashboard' in request.endpoint %}active{% endif %}">
<i class="fas fa-th-large"></i>
<span>Dashboard</span>
</a>
</li>
<li>
<a href="{{ url_for('tryouts.list_tryouts') }}" class="{% if request.endpoint and 'tryouts' in request.endpoint and request.endpoint != 'tryouts.create_tryout' %}active{% endif %}">
<i class="fas fa-calendar-alt"></i>
<span>Tryouts</span>
</a>
</li>
{% if current_user.can_evaluate() %}
<li>
<a href="{{ url_for('evaluations.list_evaluations') }}" class="{% if request.endpoint and 'evaluations' in request.endpoint %}active{% endif %}">
<i class="fas fa-clipboard-check"></i>
<span>Evaluations</span>
</a>
</li>
{% endif %}
<li>
<a href="{{ url_for('teams.list_teams') }}" class="{% if request.endpoint and 'teams' in request.endpoint %}active{% endif %}">
<i class="fas fa-users-cog"></i>
<span>Teams</span>
</a>
</li>
{% if current_user.can_manage_users() %}
<li>
<a href="{{ url_for('users.list_users') }}" class="{% if request.endpoint and 'users' in request.endpoint and request.endpoint != 'users.profile' %}active{% endif %}">
<i class="fas fa-users-cog"></i>
<span>Manage Users</span>
</a>
</li>
{% endif %}
<li>
<a href="{{ url_for('users.profile') }}" class="{% if request.endpoint == 'users.profile' %}active{% endif %}">
<i class="fas fa-user"></i>
<span>My Profile</span>
</a>
</li>
<li class="nav-divider"></li>
<li>
<a href="{{ url_for('auth.logout') }}" class="logout-link">
<i class="fas fa-sign-out-alt"></i>
<span>Logout</span>
</a>
</li>
</ul>
</nav>
<div class="main-content" id="mainContent">
<header class="top-bar">
<button class="sidebar-toggle" id="sidebarToggle" onclick="toggleSidebar()">
<i class="fas fa-bars"></i>
</button>
<div class="page-header">
<h1>{% block page_title %}Dashboard{% endblock %}</h1>
{% block breadcrumb %}{% endblock %}
</div>
{% block header_actions %}{% endblock %}
</header>
<div class="flash-messages">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }} alert-dismissible">
<span>{{ message }}</span>
<button type="button" class="alert-close" onclick="this.parentElement.remove()">&times;</button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
</div>
<div class="content">
{% block content %}{% endblock %}
</div>
</div>
{% else %}
<div class="auth-wrapper">
<div class="flash-messages">
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }} alert-dismissible">
<span>{{ message }}</span>
<button type="button" class="alert-close" onclick="this.parentElement.remove()">&times;</button>
</div>
{% endfor %}
{% endif %}
{% endwith %}
</div>
<div class="auth-container">
<div class="auth-header">
<i class="fas fa-trophy"></i>
<h2>TryoutPro</h2>
<p>Team Tryout Management System</p>
</div>
{% block auth_content %}{% endblock %}
</div>
</div>
{% endif %}
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
</body>
</html>
+53
View File
@@ -0,0 +1,53 @@
{% extends "layouts/base.html" %}
{% block title %}Create Tryout - TryoutPro{% endblock %}
{% block page_title %}Create Tryout{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / <a href="{{ url_for('tryouts.list_tryouts') }}">Tryouts</a> / Create</span>{% endblock %}
{% block content %}
<div class="card">
<div class="card-body">
<form method="POST" action="{{ url_for('tryouts.create_tryout') }}" class="form">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="form-row">
<div class="form-group col-12">
<label for="title">Tryout Title</label>
<input type="text" id="title" name="title" placeholder="e.g., Spring Season Tryouts" required>
</div>
</div>
<div class="form-row">
<div class="form-group col-6">
<label for="date">Date</label>
<input type="date" id="date" name="date" required>
</div>
<div class="form-group col-6">
<label for="location">Location</label>
<input type="text" id="location" name="location" placeholder="e.g., Main Field">
</div>
</div>
<div class="form-row">
<div class="form-group col-6">
<label for="max_players">Max Players</label>
<input type="number" id="max_players" name="max_players" placeholder="Leave blank for unlimited" min="1">
</div>
<div class="form-group col-6">
<label for="target_org_team_id">Target Team</label>
<select id="target_org_team_id" name="target_org_team_id" class="form-select">
<option value="">-- No target team --</option>
{% for team in org_teams %}
<option value="{{ team.id }}">{{ team.name }}{% if team.coach %} (Coach: {{ team.coach.full_name }}){% endif %}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group">
<label for="description">Description</label>
<textarea id="description" name="description" rows="4" placeholder="Enter any details about the tryout..."></textarea>
</div>
<div class="form-actions">
<a href="{{ url_for('tryouts.list_tryouts') }}" class="btn btn-secondary">Cancel</a>
<button type="submit" class="btn btn-primary">Create Tryout</button>
</div>
</form>
</div>
</div>
{% endblock %}
+52
View File
@@ -0,0 +1,52 @@
{% extends "layouts/base.html" %}
{% block title %}Create User - TryoutPro{% endblock %}
{% block page_title %}Create User{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / <a href="{{ url_for('users.list_users') }}">Users</a> / Create</span>{% endblock %}
{% block content %}
<div class="card">
<div class="card-body">
<form method="POST" action="{{ url_for('users.create_user') }}" class="form">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="form-row">
<div class="form-group col-6">
<label for="full_name">Full Name</label>
<input type="text" id="full_name" name="full_name" placeholder="Enter full name" required>
</div>
<div class="form-group col-6">
<label for="username">Username</label>
<input type="text" id="username" name="username" placeholder="Choose username" required>
</div>
</div>
<div class="form-row">
<div class="form-group col-6">
<label for="email">Email</label>
<input type="email" id="email" name="email" placeholder="Enter email" required>
</div>
<div class="form-group col-3">
<label for="phone">Phone</label>
<input type="tel" id="phone" name="phone" placeholder="Phone number">
</div>
<div class="form-group col-3">
<label for="role">Role</label>
<select id="role" name="role" required>
{% for r in roles %}
<option value="{{ r }}">{{ r | capitalize }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-row">
<div class="form-group col-6">
<label for="password">Password</label>
<input type="password" id="password" name="password" placeholder="Set password" required minlength="6">
</div>
</div>
<div class="form-actions">
<a href="{{ url_for('users.list_users') }}" class="btn btn-secondary">Cancel</a>
<button type="submit" class="btn btn-primary">Create User</button>
</div>
</form>
</div>
</div>
{% endblock %}
+330
View File
@@ -0,0 +1,330 @@
{% extends "layouts/base.html" %}
{% block title %}Dashboard - TryoutPro{% endblock %}
{% block page_title %}Dashboard{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / Dashboard</span>{% endblock %}
{% block content %}
<div class="dashboard">
{% if user.role == 'president' %}
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon bg-primary">
<i class="fas fa-users"></i>
</div>
<div class="stat-info">
<h3>{{ stats.total_users }}</h3>
<p>Total Users</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon bg-success">
<i class="fas fa-user-friends"></i>
</div>
<div class="stat-info">
<h3>{{ stats.total_players }}</h3>
<p>Players</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon bg-warning">
<i class="fas fa-calendar-check"></i>
</div>
<div class="stat-info">
<h3>{{ stats.total_tryouts }}</h3>
<p>Total Tryouts</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon bg-info">
<i class="fas fa-clipboard-list"></i>
</div>
<div class="stat-info">
<h3>{{ stats.total_evaluations }}</h3>
<p>Evaluations</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon bg-danger">
<i class="fas fa-play-circle"></i>
</div>
<div class="stat-info">
<h3>{{ stats.active_tryouts }}</h3>
<p>Active Tryouts</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon bg-secondary">
<i class="fas fa-clock"></i>
</div>
<div class="stat-info">
<h3>{{ stats.upcoming_tryouts }}</h3>
<p>Upcoming</p>
</div>
</div>
</div>
<div class="dashboard-grid">
<div class="card">
<div class="card-header">
<h3><i class="fas fa-user-plus"></i> Recent Users</h3>
</div>
<div class="card-body">
<table class="table">
<thead>
<tr><th>Name</th><th>Role</th><th>Joined</th></tr>
</thead>
<tbody>
{% for u in stats.recent_users %}
<tr>
<td>{{ u.full_name }}</td>
<td><span class="badge badge-{{ u.role }}">{{ u.role }}</span></td>
<td>{{ u.created_at.strftime('%m/%d/%Y') }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="card">
<div class="card-header">
<h3><i class="fas fa-calendar-alt"></i> Recent Tryouts</h3>
</div>
<div class="card-body">
<table class="table">
<thead>
<tr><th>Title</th><th>Date</th><th>Status</th></tr>
</thead>
<tbody>
{% for t in stats.recent_tryouts %}
<tr>
<td><a href="{{ url_for('tryouts.view_tryout', tryout_id=t.id) }}">{{ t.title }}</a></td>
<td>{{ t.date.strftime('%m/%d/%Y') }}</td>
<td><span class="badge badge-{{ t.status }}">{{ t.status }}</span></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% elif user.role == 'manager' %}
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon bg-primary">
<i class="fas fa-calendar-alt"></i>
</div>
<div class="stat-info">
<h3>{{ stats.total_tryouts }}</h3>
<p>My Tryouts</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon bg-success">
<i class="fas fa-play-circle"></i>
</div>
<div class="stat-info">
<h3>{{ stats.active_tryouts }}</h3>
<p>Active</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon bg-info">
<i class="fas fa-clipboard-check"></i>
</div>
<div class="stat-info">
<h3>{{ stats.total_evaluations }}</h3>
<p>My Evaluations</p>
</div>
</div>
</div>
<div class="card mt-4">
<div class="card-header">
<h3><i class="fas fa-calendar"></i> My Tryouts</h3>
<a href="{{ url_for('tryouts.create_tryout') }}" class="btn btn-sm btn-primary">+ New Tryout</a>
</div>
<div class="card-body">
<table class="table">
<thead>
<tr><th>Title</th><th>Date</th><th>Status</th><th>Players</th><th>Actions</th></tr>
</thead>
<tbody>
{% for t in stats.my_tryouts %}
<tr>
<td>{{ t.title }}</td>
<td>{{ t.date.strftime('%m/%d/%Y') }}</td>
<td><span class="badge badge-{{ t.status }}">{{ t.status }}</span></td>
<td>{{ t.registrations.count() }}</td>
<td><a href="{{ url_for('tryouts.view_tryout', tryout_id=t.id) }}" class="btn btn-sm btn-outline">View</a></td>
</tr>
{% endfor %}
{% if not stats.my_tryouts %}
<tr><td colspan="5" class="text-center">No tryouts yet. <a href="{{ url_for('tryouts.create_tryout') }}">Create one</a></td></tr>
{% endif %}
</tbody>
</table>
</div>
</div>
{% elif user.role == 'coach' %}
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon bg-primary">
<i class="fas fa-clipboard-check"></i>
</div>
<div class="stat-info">
<h3>{{ stats.my_evaluations }}</h3>
<p>Evaluations Done</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon bg-warning">
<i class="fas fa-hourglass-half"></i>
</div>
<div class="stat-info">
<h3>{{ stats.pending_evaluations }}</h3>
<p>Pending</p>
</div>
</div>
</div>
<div class="card mt-4">
<div class="card-header">
<h3><i class="fas fa-history"></i> Recent Evaluations</h3>
</div>
<div class="card-body">
<table class="table">
<thead>
<tr><th>Player</th><th>Tryout</th><th>Score</th><th>Date</th></tr>
</thead>
<tbody>
{% for e in stats.my_recent_evaluations %}
<tr>
<td>{{ e.player.full_name }}</td>
<td>{{ e.tryout.title }}</td>
<td><span class="score">{{ e.overall_score }}</span></td>
<td>{{ e.created_at.strftime('%m/%d/%Y') }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% elif user.role == 'player' %}
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon bg-primary">
<i class="fas fa-calendar-check"></i>
</div>
<div class="stat-info">
<h3>{{ stats.my_tryouts }}</h3>
<p>My Tryouts</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon bg-success">
<i class="fas fa-star"></i>
</div>
<div class="stat-info">
<h3>{{ "%.1f"|format(stats.average_score) if stats.average_score else 'N/A' }}</h3>
<p>Avg Score</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon bg-info">
<i class="fas fa-file-alt"></i>
</div>
<div class="stat-info">
<h3>{{ stats.my_evaluations }}</h3>
<p>Evaluations</p>
</div>
</div>
</div>
<div class="dashboard-grid">
<div class="card">
<div class="card-header">
<h3><i class="fas fa-clipboard-list"></i> My Registrations</h3>
</div>
<div class="card-body">
<table class="table">
<thead><tr><th>Tryout</th><th>Date</th><th>Status</th></tr></thead>
<tbody>
{% for r in stats.my_registrations %}
<tr>
<td><a href="{{ url_for('tryouts.view_tryout', tryout_id=r.tryout.id) }}">{{ r.tryout.title }}</a></td>
<td>{{ r.tryout.date.strftime('%m/%d/%Y') }}</td>
<td><span class="badge badge-{{ r.status }}">{{ r.status }}</span></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="card">
<div class="card-header">
<h3><i class="fas fa-star"></i> My Evaluations</h3>
</div>
<div class="card-body">
<table class="table">
<thead><tr><th>Tryout</th><th>Evaluator</th><th>Score</th><th>Date</th></tr></thead>
<tbody>
{% for e in stats.my_eval_list %}
<tr>
<td>{{ e.tryout.title }}</td>
<td>{{ e.evaluator.full_name }}</td>
<td><span class="score">{{ e.overall_score }}</span></td>
<td>{{ e.created_at.strftime('%m/%d/%Y') }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% elif user.role == 'scout' %}
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon bg-primary">
<i class="fas fa-user-friends"></i>
</div>
<div class="stat-info">
<h3>{{ stats.total_players }}</h3>
<p>Total Players</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon bg-info">
<i class="fas fa-clipboard-list"></i>
</div>
<div class="stat-info">
<h3>{{ stats.total_evaluations }}</h3>
<p>Total Evaluations</p>
</div>
</div>
</div>
<div class="card mt-4">
<div class="card-header">
<h3><i class="fas fa-trophy"></i> Top Rated Players</h3>
</div>
<div class="card-body">
<table class="table">
<thead><tr><th>#</th><th>Player</th><th>Avg Score</th></tr></thead>
<tbody>
{% for player, score in stats.top_players %}
<tr>
<td>{{ loop.index }}</td>
<td>{{ player.full_name }}</td>
<td><span class="score">{{ score }}</span></td>
</tr>
{% endfor %}
{% if not stats.top_players %}
<tr><td colspan="3" class="text-center">No evaluations yet.</td></tr>
{% endif %}
</tbody>
</table>
</div>
</div>
{% endif %}
</div>
{% endblock %}
+75
View File
@@ -0,0 +1,75 @@
{% extends "layouts/base.html" %}
{% block title %}Edit Profile - TryoutPro{% endblock %}
{% block page_title %}Edit Profile{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / <a href="{{ url_for('users.profile') }}">Profile</a> / Edit</span>{% endblock %}
{% block content %}
<div class="card">
<div class="card-body">
<form method="POST" action="{{ url_for('users.edit_profile') }}" class="form">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="form-row">
<div class="form-group col-6">
<label for="full_name">Full Name</label>
<input type="text" id="full_name" name="full_name" value="{{ user.full_name }}" required>
</div>
<div class="form-group col-6">
<label for="email">Email</label>
<input type="email" id="email" name="email" value="{{ user.email }}" required>
</div>
</div>
<div class="form-row">
<div class="form-group col-6">
<label for="phone">Phone</label>
<input type="tel" id="phone" name="phone" value="{{ user.phone or '' }}">
</div>
</div>
<hr class="section-divider">
<h4 class="section-title"><i class="fas fa-gamepad"></i> E-Sports Profile</h4>
<p class="text-muted small">Update your competitive gaming profile for tryouts.</p>
<div class="form-group">
<label><i class="fas fa-headset"></i> Games You Play</label>
<div class="checkbox-grid">
{% for game in esport_games %}
{% set games_list = user.get_games_list() %}
<label class="checkbox-label">
<input type="checkbox" name="games" value="{{ game }}" {% if game in games_list %}checked{% endif %}>
<span>{{ game }}</span>
</label>
{% endfor %}
</div>
<small class="form-text text-muted">Select all games you're signing in for.</small>
</div>
<div class="form-row">
<div class="form-group col-4">
<label for="trn_username"><i class="fas fa-chart-line"></i> TRN Username</label>
<input type="text" id="trn_username" name="trn_username" value="{{ user.trn_username or '' }}" placeholder="Your Tracker Network username">
<small class="form-text text-muted">Others can click your TRN to view your stats on tracker.gg.</small>
</div>
<div class="form-group col-4">
<label for="discord_username"><i class="fab fa-discord"></i> Discord Username</label>
<input type="text" id="discord_username" name="discord_username" value="{{ user.discord_username or '' }}" placeholder="e.g. Name#1234">
</div>
<div class="form-group col-4">
<label for="league_os_profile"><i class="fas fa-link"></i> League OS Connection</label>
<input type="text" id="league_os_profile" name="league_os_profile" value="{{ user.league_os_profile or '' }}" placeholder="League OS profile link or ID">
</div>
</div>
<hr class="section-divider">
<h4 class="section-title"><i class="fas fa-lock"></i> Change Password</h4>
<p class="text-muted small">Leave blank to keep your current password.</p>
<div class="form-group">
<label for="password">New Password</label>
<input type="password" id="password" name="password" placeholder="Enter new password">
</div>
<div class="form-actions">
<a href="{{ url_for('users.profile') }}" class="btn btn-secondary">Cancel</a>
<button type="submit" class="btn btn-primary">Save Changes</button>
</div>
</form>
</div>
</div>
{% endblock %}
+55
View File
@@ -0,0 +1,55 @@
{% extends "layouts/base.html" %}
{% block title %}Edit Tryout - TryoutPro{% endblock %}
{% block page_title %}Edit Tryout{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / <a href="{{ url_for('tryouts.list_tryouts') }}">Tryouts</a> / <a href="{{ url_for('tryouts.view_tryout', tryout_id=tryout.id) }}">{{ tryout.title }}</a> / Edit</span>{% endblock %}
{% block content %}
<div class="card">
<div class="card-body">
<form method="POST" action="{{ url_for('tryouts.edit_tryout', tryout_id=tryout.id) }}" class="form">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="form-row">
<div class="form-group col-12">
<label for="title">Tryout Title</label>
<input type="text" id="title" name="title" value="{{ tryout.title }}" placeholder="e.g., Spring Season Tryouts" required>
</div>
</div>
<div class="form-row">
<div class="form-group col-6">
<label for="date">Date</label>
<input type="date" id="date" name="date" value="{{ tryout.date.strftime('%Y-%m-%d') }}" required>
</div>
<div class="form-group col-6">
<label for="location">Location</label>
<input type="text" id="location" name="location" value="{{ tryout.location or '' }}" placeholder="e.g., Main Field">
</div>
</div>
<div class="form-row">
<div class="form-group col-6">
<label for="max_players">Max Players</label>
<input type="number" id="max_players" name="max_players" value="{{ tryout.max_players or '' }}" placeholder="Leave blank for unlimited" min="1">
</div>
<div class="form-group col-6">
<label for="target_org_team_id">Target Team</label>
<select id="target_org_team_id" name="target_org_team_id" class="form-select">
<option value="">-- No target team --</option>
{% for team in org_teams %}
<option value="{{ team.id }}" {% if tryout.target_org_team_id == team.id %}selected{% endif %}>
{{ team.name }}{% if team.coach %} (Coach: {{ team.coach.full_name }}){% endif %}
</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group">
<label for="description">Description</label>
<textarea id="description" name="description" rows="4" placeholder="Enter any details about the tryout...">{{ tryout.description or '' }}</textarea>
</div>
<div class="form-actions">
<a href="{{ url_for('tryouts.view_tryout', tryout_id=tryout.id) }}" class="btn btn-secondary">Cancel</a>
<button type="submit" class="btn btn-primary">Save Changes</button>
</div>
</form>
</div>
</div>
{% endblock %}
+83
View File
@@ -0,0 +1,83 @@
{% extends "layouts/base.html" %}
{% block title %}Edit {{ user.full_name }} - TryoutPro{% endblock %}
{% block page_title %}Edit User{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / <a href="{{ url_for('users.list_users') }}">Users</a> / Edit</span>{% endblock %}
{% block content %}
<div class="card">
<div class="card-body">
<form method="POST" action="{{ url_for('users.edit_user', user_id=user.id) }}" class="form">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="form-row">
<div class="form-group col-6">
<label for="full_name">Full Name</label>
<input type="text" id="full_name" name="full_name" value="{{ user.full_name }}" required>
</div>
<div class="form-group col-6">
<label for="email">Email</label>
<input type="email" id="email" name="email" value="{{ user.email }}" required>
</div>
</div>
<div class="form-row">
<div class="form-group col-4">
<label for="phone">Phone</label>
<input type="tel" id="phone" name="phone" value="{{ user.phone or '' }}">
</div>
<div class="form-group col-4">
<label for="role">Role</label>
<select id="role" name="role" required>
{% for r in roles %}
<option value="{{ r }}" {% if user.role == r %}selected{% endif %}>{{ r | capitalize }}</option>
{% endfor %}
</select>
</div>
<div class="form-group col-4">
<label for="is_active_account">
<input type="checkbox" id="is_active_account" name="is_active_account" {% if user.is_active_account %}checked{% endif %}>
Active Account
</label>
</div>
</div>
<hr class="section-divider">
<h4 class="section-title"><i class="fas fa-gamepad"></i> E-Sports Profile</h4>
<div class="form-group">
<label>Games</label>
<div class="checkbox-grid">
{% for game in esport_games %}
{% set games_list = user.get_games_list() %}
<label class="checkbox-label">
<input type="checkbox" name="games" value="{{ game }}" {% if game in games_list %}checked{% endif %}>
<span>{{ game }}</span>
</label>
{% endfor %}
</div>
</div>
<div class="form-row">
<div class="form-group col-4">
<label for="trn_username">TRN (Tracker Network) Username</label>
<input type="text" id="trn_username" name="trn_username" value="{{ user.trn_username or '' }}" placeholder="Tracker Network username">
</div>
<div class="form-group col-4">
<label for="discord_username">Discord Username</label>
<input type="text" id="discord_username" name="discord_username" value="{{ user.discord_username or '' }}" placeholder="e.g. Name#1234">
</div>
<div class="form-group col-4">
<label for="league_os_profile">League OS Connection</label>
<input type="text" id="league_os_profile" name="league_os_profile" value="{{ user.league_os_profile or '' }}" placeholder="League OS profile link or ID">
</div>
</div>
<div class="form-group">
<label for="password">New Password <small>(leave blank to keep current)</small></label>
<input type="password" id="password" name="password" placeholder="Enter new password">
</div>
<div class="form-actions">
<a href="{{ url_for('users.list_users') }}" class="btn btn-secondary">Cancel</a>
<button type="submit" class="btn btn-primary">Update User</button>
</div>
</form>
</div>
</div>
{% endblock %}
+120
View File
@@ -0,0 +1,120 @@
{% extends "layouts/base.html" %}
{% block title %}Evaluate {{ player.full_name }} - TryoutPro{% endblock %}
{% block page_title %}Evaluate {{ player.full_name }}{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / <a href="{{ url_for('tryouts.view_tryout', tryout_id=tryout.id) }}">{{ tryout.title }}</a> / Evaluate</span>{% endblock %}
{% block content %}
<div class="dashboard-grid">
<div class="card">
<div class="card-header">
<h3><i class="fas fa-clipboard-list"></i> Player Evaluation</h3>
<span class="badge badge-info">{{ tryout.title }}</span>
</div>
<div class="card-body">
<div class="eval-player-info mb-4">
<div class="user-avatar avatar-lg">{{ player.full_name[:2] | upper }}</div>
<div>
<h3>{{ player.full_name }}</h3>
<p class="text-muted">{{ player.email }} | {{ player.phone or 'No phone' }}</p>
</div>
</div>
{% if existing_eval %}
<div class="alert alert-info">
<i class="fas fa-info-circle"></i> You have already evaluated this player. Your previous scores are shown below.
</div>
{% endif %}
<form method="POST" action="{{ url_for('evaluations.evaluate_player', tryout_id=tryout.id, player_id=player.id) }}" class="form">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="form-row">
<div class="form-group col-4">
<label for="speed_score">Speed (1-10)</label>
<div class="score-input">
<input type="range" id="speed_score" name="speed_score" min="1" max="10" value="{{ existing_eval.speed_score or 5 }}" oninput="this.nextElementSibling.textContent = this.value">
<span class="range-value">{{ existing_eval.speed_score or 5 }}</span>
</div>
</div>
<div class="form-group col-4">
<label for="agility_score">Agility (1-10)</label>
<div class="score-input">
<input type="range" id="agility_score" name="agility_score" min="1" max="10" value="{{ existing_eval.agility_score or 5 }}" oninput="this.nextElementSibling.textContent = this.value">
<span class="range-value">{{ existing_eval.agility_score or 5 }}</span>
</div>
</div>
<div class="form-group col-4">
<label for="technique_score">Technique (1-10)</label>
<div class="score-input">
<input type="range" id="technique_score" name="technique_score" min="1" max="10" value="{{ existing_eval.technique_score or 5 }}" oninput="this.nextElementSibling.textContent = this.value">
<span class="range-value">{{ existing_eval.technique_score or 5 }}</span>
</div>
</div>
</div>
<div class="form-row">
<div class="form-group col-4">
<label for="teamwork_score">Teamwork (1-10)</label>
<div class="score-input">
<input type="range" id="teamwork_score" name="teamwork_score" min="1" max="10" value="{{ existing_eval.teamwork_score or 5 }}" oninput="this.nextElementSibling.textContent = this.value">
<span class="range-value">{{ existing_eval.teamwork_score or 5 }}</span>
</div>
</div>
<div class="form-group col-4">
<label for="attitude_score">Attitude (1-10)</label>
<div class="score-input">
<input type="range" id="attitude_score" name="attitude_score" min="1" max="10" value="{{ existing_eval.attitude_score or 5 }}" oninput="this.nextElementSibling.textContent = this.value">
<span class="range-value">{{ existing_eval.attitude_score or 5 }}</span>
</div>
</div>
<div class="form-group col-4">
<label for="position_recommendation">Recommended Position</label>
<input type="text" id="position_recommendation" name="position_recommendation" value="{{ existing_eval.position_recommendation or '' }}" placeholder="e.g., Forward, Defense">
</div>
</div>
<div class="form-group">
<label for="comments">Comments</label>
<textarea id="comments" name="comments" rows="4" placeholder="Enter your evaluation notes...">{{ existing_eval.comments or '' }}</textarea>
</div>
<div class="form-actions">
<a href="{{ url_for('tryouts.view_tryout', tryout_id=tryout.id) }}" class="btn btn-secondary">Cancel</a>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> {% if existing_eval %}Update Evaluation{% else %}Submit Evaluation{% endif %}
</button>
</div>
</form>
</div>
</div>
{% if evaluators %}
<div class="card">
<div class="card-header">
<h3><i class="fas fa-users"></i> All Evaluations for {{ player.full_name }}</h3>
</div>
<div class="card-body">
<table class="table">
<thead>
<tr><th>Evaluator</th><th>Speed</th><th>Agility</th><th>Technique</th><th>Teamwork</th><th>Attitude</th><th>Overall</th><th>Position</th></tr>
</thead>
<tbody>
{% for entry in evaluators %}
<tr>
<td>{{ entry.evaluator.full_name }}</td>
<td>{{ entry.eval.speed_score or '-' }}</td>
<td>{{ entry.eval.agility_score or '-' }}</td>
<td>{{ entry.eval.technique_score or '-' }}</td>
<td>{{ entry.eval.teamwork_score or '-' }}</td>
<td>{{ entry.eval.attitude_score or '-' }}</td>
<td><span class="score">{{ entry.eval.overall_score or '-' }}</span></td>
<td>{{ entry.eval.position_recommendation or '-' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
</div>
{% endblock %}
+81
View File
@@ -0,0 +1,81 @@
{% extends "layouts/base.html" %}
{% block title %}Evaluations - TryoutPro{% endblock %}
{% block page_title %}Evaluations{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / Evaluations</span>{% endblock %}
{% block content %}
{% if current_user.role == 'president' and player_scores %}
<div class="stats-grid mb-4">
{% for pid, data in player_scores.items() %}
<div class="stat-card stat-card-sm">
<div class="stat-icon bg-info">
<i class="fas fa-user"></i>
</div>
<div class="stat-info">
<h3>{{ data.player.full_name[:12] }}</h3>
<p>Avg: <strong>{{ data.avg }}</strong> / {{ data.count }} evals</p>
</div>
</div>
{% endfor %}
</div>
{% endif %}
<div class="card">
<div class="card-header">
<h3><i class="fas fa-clipboard-list"></i>
{% if current_user.role == 'player' %}
My Evaluations
{% else %}
All Evaluations
{% endif %}
</h3>
</div>
<div class="card-body">
<div class="table-container">
<table class="table">
<thead>
<tr>
<th>Tryout</th>
<th>Player</th>
<th>Evaluator</th>
<th>Speed</th>
<th>Agility</th>
<th>Technique</th>
<th>Teamwork</th>
<th>Attitude</th>
<th>Overall</th>
<th>Position</th>
<th>Date</th>
</tr>
</thead>
<tbody>
{% for eval in evaluations %}
<tr>
<td>{{ eval.tryout.title }}</td>
<td>{{ eval.player.full_name }}</td>
<td>{{ eval.evaluator.full_name }}</td>
<td>{{ eval.speed_score or '-' }}</td>
<td>{{ eval.agility_score or '-' }}</td>
<td>{{ eval.technique_score or '-' }}</td>
<td>{{ eval.teamwork_score or '-' }}</td>
<td>{{ eval.attitude_score or '-' }}</td>
<td><span class="score">{{ eval.overall_score or '-' }}</span></td>
<td><span class="badge badge-info">{{ eval.position_recommendation or 'N/A' }}</span></td>
<td>{{ eval.created_at.strftime('%m/%d/%Y') }}</td>
</tr>
{% else %}
<tr>
<td colspan="11" class="text-center">
<div class="empty-state">
<i class="fas fa-clipboard"></i>
<h3>No evaluations yet</h3>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}
+17
View File
@@ -0,0 +1,17 @@
{% extends "layouts/base.html" %}
{% block title %}Login - TryoutPro{% endblock %}
{% block auth_content %}
<form method="POST" action="{{ url_for('auth.login') }}" class="auth-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="form-group">
<label for="username"><i class="fas fa-user"></i> Username</label>
<input type="text" id="username" name="username" placeholder="Enter your username" required>
</div>
<div class="form-group">
<label for="password"><i class="fas fa-lock"></i> Password</label>
<input type="password" id="password" name="password" placeholder="Enter your password" required>
</div>
<button type="submit" class="btn btn-primary btn-block">Sign In</button>
<p class="auth-link">Don't have an account? <a href="{{ url_for('auth.register') }}">Register here</a></p>
</form>
{% endblock %}
+59
View File
@@ -0,0 +1,59 @@
{% extends "layouts/base.html" %}
{% block title %}Players to Evaluate - TryoutPro{% endblock %}
{% block page_title %}Players to Evaluate{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / <a href="{{ url_for('tryouts.view_tryout', tryout_id=tryout.id) }}">{{ tryout.title }}</a> / Evaluate</span>{% endblock %}
{% block content %}
<div class="card">
<div class="card-header">
<h3><i class="fas fa-users"></i> Players in {{ tryout.title }}</h3>
</div>
<div class="card-body">
<div class="table-container">
<table class="table">
<thead>
<tr>
<th>Player</th>
<th>Contact</th>
<th>Attendance</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for entry in players %}
<tr>
<td>
<div class="user-mini">
<div class="avatar-sm">{{ entry.player.full_name[:2] | upper }}</div>
<span>{{ entry.player.full_name }}</span>
</div>
</td>
<td>{{ entry.player.email }}</td>
<td>
<span class="badge badge-{{ entry.registration.status }}">{{ entry.registration.status }}</span>
</td>
<td>
{% if entry.evaluated %}
<span class="badge badge-success">Evaluated</span>
{% else %}
<span class="badge badge-warning">Not Evaluated</span>
{% endif %}
</td>
<td>
<a href="{{ url_for('evaluations.evaluate_player', tryout_id=tryout.id, player_id=entry.player.id) }}" class="btn btn-sm btn-primary">
<i class="fas fa-clipboard"></i> {% if entry.evaluated %}View/Edit{% else %}Evaluate{% endif %}
</a>
</td>
</tr>
{% else %}
<tr>
<td colspan="5" class="text-center">No players registered for this tryout.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}
+147
View File
@@ -0,0 +1,147 @@
{% extends "layouts/base.html" %}
{% block title %}My Profile - TryoutPro{% endblock %}
{% block page_title %}My Profile{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / Profile</span>{% endblock %}
{% block header_actions %}
<div class="header-actions">
<a href="{{ url_for('users.edit_profile') }}" class="btn btn-sm btn-primary">
<i class="fas fa-edit"></i> Edit Profile
</a>
</div>
{% endblock %}
{% block content %}
<div class="dashboard-grid">
<div class="card">
<div class="card-header">
<h3><i class="fas fa-user-circle"></i> Account Information</h3>
</div>
<div class="card-body">
<div class="profile-header">
<div class="user-avatar avatar-xl">{{ user.full_name[:2] | upper }}</div>
<div class="profile-info">
<h2>{{ user.full_name }}</h2>
<span class="badge badge-{{ user.role }} badge-lg">{{ user.role | capitalize }}</span>
<p class="text-muted">Member since {{ user.created_at.strftime('%B %Y') }}</p>
</div>
</div>
<div class="detail-grid mt-4">
<div class="detail-item">
<span class="detail-label">Username</span>
<span class="detail-value">{{ user.username }}</span>
</div>
<div class="detail-item">
<span class="detail-label">Email</span>
<span class="detail-value">{{ user.email }}</span>
</div>
<div class="detail-item">
<span class="detail-label">Phone</span>
<span class="detail-value">{{ user.phone or 'Not provided' }}</span>
</div>
<div class="detail-item">
<span class="detail-label">Account Status</span>
<span class="detail-value">
{% if user.is_active_account %}
<span class="badge badge-success">Active</span>
{% else %}
<span class="badge badge-danger">Inactive</span>
{% endif %}
</span>
</div>
</div>
</div>
</div>
<!-- E-Sports Profile Card -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-gamepad"></i> E-Sports Profile</h3>
</div>
<div class="card-body">
<div class="detail-grid">
<div class="detail-item">
<span class="detail-label"><i class="fas fa-headset"></i> Games</span>
<span class="detail-value">
{% set games_list = user.get_games_list() %}
{% if games_list %}
{% for game in games_list %}
<span class="badge badge-esport">{{ game }}</span>
{% endfor %}
{% else %}
<span class="text-muted">Not specified</span>
{% endif %}
</span>
</div>
<div class="detail-item">
<span class="detail-label"><i class="fas fa-chart-line"></i> TRN (Tracker Network)</span>
<span class="detail-value">
{% if user.trn_username %}
<a href="https://tracker.gg" target="_blank" rel="noopener noreferrer" class="trn-link">
<i class="fas fa-external-link-alt"></i> {{ user.trn_username }}
</a>
{% else %}
<span class="text-muted">Not connected</span>
{% endif %}
</span>
</div>
<div class="detail-item">
<span class="detail-label"><i class="fab fa-discord"></i> Discord</span>
<span class="detail-value">
{% if user.discord_username %}
{{ user.discord_username }}
{% else %}
<span class="text-muted">Not connected</span>
{% endif %}
</span>
</div>
<div class="detail-item">
<span class="detail-label"><i class="fas fa-link"></i> League OS</span>
<span class="detail-value">
{% if user.league_os_profile %}
<a href="{{ user.league_os_profile }}" target="_blank" rel="noopener noreferrer" class="trn-link">
<i class="fas fa-external-link-alt"></i> {{ user.league_os_profile }}
</a>
{% else %}
<span class="text-muted">Not connected</span>
{% endif %}
</span>
</div>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<h3><i class="fas fa-chart-bar"></i> My Statistics</h3>
</div>
<div class="card-body">
{% if user.role == 'player' %}
<div class="profile-stats">
<div class="stat-item">
<h4>{{ user.tryout_registrations.count() }}</h4>
<p>Tryouts Registered</p>
</div>
<div class="stat-item">
<h4>{{ user.evaluations_received.count() }}</h4>
<p>Evaluations Received</p>
</div>
<div class="stat-item">
<h4>{{ user.team_assignments.count() }}</h4>
<p>Team Assignments</p>
</div>
</div>
{% elif user.can_evaluate() %}
<div class="profile-stats">
<div class="stat-item">
<h4>{{ user.evaluations_given.count() }}</h4>
<p>Evaluations Given</p>
</div>
</div>
{% else %}
<p class="text-muted">No statistics available for this role.</p>
{% endif %}
</div>
</div>
</div>
{% endblock %}
+66
View File
@@ -0,0 +1,66 @@
{% extends "layouts/base.html" %}
{% block title %}Register - TryoutPro{% endblock %}
{% block auth_content %}
<form method="POST" action="{{ url_for('auth.register') }}" class="auth-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="form-group">
<label for="full_name"><i class="fas fa-id-card"></i> Full Name</label>
<input type="text" id="full_name" name="full_name" placeholder="Enter your full name" required>
</div>
<div class="form-group">
<label for="username"><i class="fas fa-user"></i> Username</label>
<input type="text" id="username" name="username" placeholder="Choose a username" required>
</div>
<div class="form-group">
<label for="email"><i class="fas fa-envelope"></i> Email</label>
<input type="email" id="email" name="email" placeholder="Enter your email" required>
</div>
<div class="form-group">
<label for="phone"><i class="fas fa-phone"></i> Phone (Optional)</label>
<input type="tel" id="phone" name="phone" placeholder="Enter your phone number">
</div>
<hr class="section-divider">
<h4 class="section-title"><i class="fas fa-gamepad"></i> E-Sports Profile</h4>
<p class="text-muted small">Set up your competitive gaming profile for tryouts.</p>
<div class="form-group">
<label><i class="fas fa-headset"></i> Games You Play</label>
<div class="checkbox-grid">
{% for game in esport_games %}
<label class="checkbox-label">
<input type="checkbox" name="games" value="{{ game }}">
<span>{{ game }}</span>
</label>
{% endfor %}
</div>
<small class="form-text text-muted">Select all games you're signing in for.</small>
</div>
<div class="form-group">
<label for="trn_username"><i class="fas fa-chart-line"></i> TRN (Tracker Network) Username</label>
<input type="text" id="trn_username" name="trn_username" placeholder="e.g. YourTrackerGGUsername">
<small class="form-text text-muted">Your public Tracker Network profile name. Others can click it to view your stats.</small>
</div>
<div class="form-group">
<label for="discord_username"><i class="fab fa-discord"></i> Discord Username</label>
<input type="text" id="discord_username" name="discord_username" placeholder="e.g. YourName#1234">
</div>
<div class="form-group">
<label for="league_os_profile"><i class="fas fa-link"></i> League OS Connection</label>
<input type="text" id="league_os_profile" name="league_os_profile" placeholder="League OS profile link or ID">
<small class="form-text text-muted">Connect your League OS profile for organized play.</small>
</div>
<hr class="section-divider">
<div class="form-group">
<label for="password"><i class="fas fa-lock"></i> Password</label>
<input type="password" id="password" name="password" placeholder="Create a password" required minlength="6">
</div>
<div class="form-group">
<label for="confirm_password"><i class="fas fa-check-circle"></i> Confirm Password</label>
<input type="password" id="confirm_password" name="confirm_password" placeholder="Confirm your password" required>
</div>
<button type="submit" class="btn btn-primary btn-block">Create Account</button>
<p class="auth-link">Already have an account? <a href="{{ url_for('auth.login') }}">Sign in</a></p>
</form>
{% endblock %}
+230
View File
@@ -0,0 +1,230 @@
{% extends "layouts/base.html" %}
{% block title %}Teams - TryoutPro{% endblock %}
{% block page_title %}Teams{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / Teams</span>{% endblock %}
{% block header_actions %}
{% if can_manage %}
<button class="btn btn-primary" onclick="showCreateForm()">
<i class="fas fa-plus"></i> New Team
</button>
{% endif %}
{% endblock %}
{% block content %}
{% if can_manage %}
<div id="createTeamForm" class="card mb-4 {% if not form_visible %}hidden{% endif %}">
<div class="card-header">
<h3>Create New Team</h3>
</div>
<div class="card-body">
<form method="POST" action="{{ url_for('teams.create_team') }}" class="form">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="form-row">
<div class="form-group col-6">
<label for="name">Team Name</label>
<input type="text" id="name" name="name" placeholder="e.g., Varsity, JV, U14" required>
</div>
<div class="form-group col-6">
<label for="coach_id">Assigned Coach</label>
<select id="coach_id" name="coach_id" class="form-select">
<option value="">-- No coach assigned --</option>
{% for coach in coaches %}
<option value="{{ coach.id }}">{{ coach.full_name }}</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" onclick="hideCreateForm()">Cancel</button>
<button type="submit" class="btn btn-primary">Create Team</button>
</div>
</form>
</div>
</div>
{% endif %}
{% for team in teams %}
<div class="card mb-4">
<div class="card-header">
<h3><i class="fas fa-users-cog"></i> {{ team.name }}</h3>
<div class="card-actions">
{% if team.coach %}
<span class="text-muted">Coach: {{ team.coach.full_name }}</span>
{% else %}
<span class="text-muted">No coach assigned</span>
{% endif %}
{% if can_manage %}
<button class="btn btn-sm btn-outline edit-team-btn" data-team-id="{{ team.id }}" data-team-name="{{ team.name }}" data-coach-id="{{ team.coach_id or '' }}">
<i class="fas fa-edit"></i> Edit
</button>
<form method="POST" action="{{ url_for('teams.delete_team', team_id=team.id) }}" class="inline-form" onsubmit="return confirm('Delete team {{ team.name }}? This will unassign it from any linked tryouts.')">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<button type="submit" class="btn btn-sm btn-danger">
<i class="fas fa-trash"></i> Delete
</button>
</form>
{% endif %}
</div>
</div>
<div class="card-body">
<div class="table-container">
<table class="table">
<thead>
<tr>
<th>Player</th>
<th>Position / Role</th>
<th>Email</th>
<th>Phone</th>
{% if can_manage %}
<th>Actions</th>
{% endif %}
</tr>
</thead>
<tbody>
{% for player in team.players %}
<tr>
<td>
<div class="user-mini">
<div class="avatar-sm">{{ player.full_name[:2] | upper }}</div>
<span>{{ player.full_name }}</span>
</div>
</td>
<td>{{ player.role | capitalize }}</td>
<td>{{ player.email }}</td>
<td>{{ player.phone or '-' }}</td>
{% if can_manage %}
<td>
<form method="POST" action="{{ url_for('teams.remove_player', team_id=team.id, player_id=player.id) }}" class="inline-form" onsubmit="return confirm('Remove {{ player.full_name }} from {{ team.name }}?')">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<button type="submit" class="btn btn-sm btn-danger">
<i class="fas fa-user-minus"></i> Remove
</button>
</form>
</td>
{% endif %}
</tr>
{% else %}
<tr>
<td colspan="{% if can_manage %}5{% else %}4{% endif %}" class="text-center">
<div class="empty-state">
<i class="fas fa-users-slash"></i>
<h4>No players assigned</h4>
{% if can_manage %}
<p>Add players to this team using the form below.</p>
{% endif %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% if can_manage %}
<hr class="my-3">
<div class="add-player-section">
<h5 class="mb-2"><i class="fas fa-user-plus"></i> Add Player to {{ team.name }}</h5>
<form method="POST" action="{{ url_for('teams.add_player', team_id=team.id) }}" class="form-inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<select name="player_id" class="form-select" required>
<option value="">-- Select a player --</option>
{% for p in all_players %}
{% if p.team_id != team.id %}
<option value="{{ p.id }}">{{ p.full_name }} {% if p.org_team %}(currently in {{ p.org_team.name }}){% endif %}</option>
{% endif %}
{% endfor %}
</select>
<button type="submit" class="btn btn-sm btn-primary ml-2">
<i class="fas fa-plus"></i> Add to Team
</button>
</form>
</div>
{% endif %}
</div>
</div>
{% else %}
<div class="card">
<div class="card-body text-center">
<div class="empty-state">
<i class="fas fa-users-cog"></i>
<h3>No teams yet</h3>
{% if can_manage %}
<p>Create organization teams and assign coaches to manage tryouts.</p>
<button class="btn btn-primary" onclick="showCreateForm()">Create Team</button>
{% else %}
<p>There are no teams to display.</p>
{% endif %}
</div>
</div>
</div>
{% endfor %}
<!-- Edit Team Modal -->
<div id="editTeamModal" class="modal hidden">
<div class="modal-backdrop" onclick="hideEditForm()"></div>
<div class="modal-content">
<div class="modal-header">
<h3>Edit Team</h3>
<button class="modal-close" onclick="hideEditForm()">&times;</button>
</div>
<div class="modal-body">
<form id="editTeamForm" method="POST" action="" class="form">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="form-group">
<label for="edit_name">Team Name</label>
<input type="text" id="edit_name" name="name" required>
</div>
<div class="form-group">
<label for="edit_coach_id">Assigned Coach</label>
<select id="edit_coach_id" name="coach_id" class="form-select">
<option value="">-- No coach assigned --</option>
{% for coach in coaches %}
<option value="{{ coach.id }}">{{ coach.full_name }}</option>
{% endfor %}
</select>
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" onclick="hideEditForm()">Cancel</button>
<button type="submit" class="btn btn-primary">Save Changes</button>
</div>
</form>
</div>
</div>
</div>
<script>
function showCreateForm() {
document.getElementById('createTeamForm').classList.remove('hidden');
}
function hideCreateForm() {
document.getElementById('createTeamForm').classList.add('hidden');
}
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');
document.getElementById('editTeamForm').action = '/teams/' + teamId + '/edit';
document.getElementById('edit_name').value = teamName;
document.getElementById('edit_coach_id').value = coachId || '';
document.getElementById('editTeamModal').classList.remove('hidden');
});
});
});
function showEditForm(teamId, teamName, coachId) {
document.getElementById('editTeamForm').action = '/teams/' + teamId + '/edit';
document.getElementById('edit_name').value = teamName;
document.getElementById('edit_coach_id').value = coachId || '';
document.getElementById('editTeamModal').classList.remove('hidden');
}
function hideEditForm() {
document.getElementById('editTeamModal').classList.add('hidden');
}
</script>
{% endblock %}
+75
View File
@@ -0,0 +1,75 @@
{% extends "layouts/base.html" %}
{% block title %}Tryouts - TryoutPro{% endblock %}
{% block page_title %}Tryouts{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / Tryouts</span>{% endblock %}
{% block header_actions %}
{% if current_user.can_manage_tryouts() %}
<a href="{{ url_for('tryouts.create_tryout') }}" class="btn btn-primary">
<i class="fas fa-plus"></i> New Tryout
</a>
{% endif %}
{% endblock %}
{% block content %}
<div class="card">
<div class="card-body">
<div class="table-container">
<table class="table">
<thead>
<tr>
<th>Title</th>
<th>Target Team</th>
<th>Date</th>
<th>Location</th>
<th>Status</th>
<th>Players</th>
<th>Evaluations</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for tryout in tryouts %}
<tr>
<td class="cell-title">{{ tryout.title }}</td>
<td>{{ tryout.target_org_team.name if tryout.target_org_team else '-' }}</td>
<td>{{ tryout.date.strftime('%b %d, %Y') }}</td>
<td>{{ tryout.location or 'TBD' }}</td>
<td>
<span class="badge badge-{{ tryout.status }}">
{{ tryout.status | replace('_', ' ') | title }}
</span>
</td>
<td>{{ tryout.registrations.count() }}</td>
<td>{{ tryout.evaluations.count() }}</td>
<td>
<a href="{{ url_for('tryouts.view_tryout', tryout_id=tryout.id) }}" class="btn btn-sm btn-outline">
<i class="fas fa-eye"></i> View
</a>
{% if current_user.can_evaluate() %}
<a href="{{ url_for('evaluations.players_to_evaluate', tryout_id=tryout.id) }}" class="btn btn-sm btn-primary">
<i class="fas fa-clipboard-check"></i> Evaluate
</a>
{% endif %}
</td>
</tr>
{% else %}
<tr>
<td colspan="8" class="text-center">
<div class="empty-state">
<i class="fas fa-calendar-times"></i>
<h3>No tryouts found</h3>
{% if current_user.can_manage_tryouts() %}
<p>Get started by creating a new tryout.</p>
<a href="{{ url_for('tryouts.create_tryout') }}" class="btn btn-primary">Create Tryout</a>
{% endif %}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}
+68
View File
@@ -0,0 +1,68 @@
{% extends "layouts/base.html" %}
{% block title %}Manage Users - TryoutPro{% endblock %}
{% block page_title %}Manage Users{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / Users</span>{% endblock %}
{% block header_actions %}
<a href="{{ url_for('users.create_user') }}" class="btn btn-primary">
<i class="fas fa-user-plus"></i> Add User
</a>
{% endblock %}
{% block content %}
<div class="card">
<div class="card-body">
<div class="table-container">
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Username</th>
<th>Email</th>
<th>Role</th>
<th>Status</th>
<th>Joined</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for u in users %}
<tr>
<td>
<div class="user-mini">
<div class="avatar-sm">{{ u.full_name[:2] | upper }}</div>
<span>{{ u.full_name }}</span>
</div>
</td>
<td>{{ u.username }}</td>
<td>{{ u.email }}</td>
<td><span class="badge badge-{{ u.role }}">{{ u.role | capitalize }}</span></td>
<td>
{% if u.is_active_account %}
<span class="badge badge-success">Active</span>
{% else %}
<span class="badge badge-danger">Inactive</span>
{% endif %}
</td>
<td>{{ u.created_at.strftime('%m/%d/%Y') }}</td>
<td>
<a href="{{ url_for('users.edit_user', user_id=u.id) }}" class="btn btn-sm btn-outline">
<i class="fas fa-edit"></i> Edit
</a>
{% if u.id != current_user.id %}
<form method="POST" action="{{ url_for('users.delete_user', user_id=u.id) }}" class="inline-form" onsubmit="return confirm('Delete {{ u.full_name }}? This cannot be undone.');">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<button type="submit" class="btn btn-sm btn-danger">
<i class="fas fa-trash"></i>
</button>
</form>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endblock %}
+278
View File
@@ -0,0 +1,278 @@
{% extends "layouts/base.html" %}
{% block title %}{{ tryout.title }} - TryoutPro{% endblock %}
{% block page_title %}{{ tryout.title }}{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / <a href="{{ url_for('tryouts.list_tryouts') }}">Tryouts</a> / {{ tryout.title }}</span>{% endblock %}
{% block content %}
<div class="tryout-detail">
<div class="card mb-4">
<div class="card-header">
<h3>Tryout Details</h3>
<div class="card-actions">
{% if current_user.can_manage_tryouts() %}
<form method="POST" action="{{ url_for('tryouts.update_status', tryout_id=tryout.id) }}" class="inline-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<select name="status" onchange="this.form.submit()" class="form-select">
<option value="upcoming" {% if tryout.status == 'upcoming' %}selected{% endif %}>Upcoming</option>
<option value="in_progress" {% if tryout.status == 'in_progress' %}selected{% endif %}>In Progress</option>
<option value="completed" {% if tryout.status == 'completed' %}selected{% endif %}>Completed</option>
</select>
</form>
{% endif %}
{% if can_edit %}
<a href="{{ url_for('tryouts.edit_tryout', tryout_id=tryout.id) }}" class="btn btn-sm btn-primary">
<i class="fas fa-edit"></i> Edit
</a>
{% endif %}
</div>
</div>
<div class="card-body">
<div class="detail-grid">
<div class="detail-item">
<span class="detail-label">Date</span>
<span class="detail-value">{{ tryout.date.strftime('%A, %B %d, %Y') }}</span>
</div>
<div class="detail-item">
<span class="detail-label">Location</span>
<span class="detail-value">{{ tryout.location or 'Not specified' }}</span>
</div>
<div class="detail-item">
<span class="detail-label">Status</span>
<span class="detail-value">
<span class="badge badge-{{ tryout.status }}">{{ tryout.status | replace('_', ' ') | title }}</span>
</span>
</div>
<div class="detail-item">
<span class="detail-label">Target Team</span>
<span class="detail-value">{{ tryout.target_org_team.name if tryout.target_org_team else 'Not specified' }}</span>
</div>
<div class="detail-item">
<span class="detail-label">Registered Players</span>
<span class="detail-value">{{ registered_players | length }}</span>
</div>
{% if tryout.description %}
<div class="detail-item full-width">
<span class="detail-label">Description</span>
<span class="detail-value">{{ tryout.description }}</span>
</div>
{% endif %}
</div>
</div>
</div>
{% if current_user.role == 'player' and not is_registered and tryout.status in ['upcoming', 'in_progress'] %}
<div class="card mb-4">
<div class="card-body text-center">
<form method="POST" action="{{ url_for('tryouts.register_for_tryout', tryout_id=tryout.id) }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<button type="submit" class="btn btn-primary btn-lg">
<i class="fas fa-user-plus"></i> Register for this Tryout
</button>
</form>
</div>
</div>
{% endif %}
{% if current_user.can_manage_tryouts() %}
<div class="card mb-4">
<div class="card-header">
<h3><i class="fas fa-user-plus"></i> Add Player to Tryout</h3>
</div>
<div class="card-body">
<form method="POST" action="{{ url_for('tryouts.register_player', tryout_id=tryout.id) }}" class="form-inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<select name="player_id" class="form-select" required>
<option value="">-- Select a player --</option>
{% for p in all_players %}
{% set is_already = registrations | selectattr('player_id', 'equalto', p.id) | list | length > 0 %}
<option value="{{ p.id }}" {% if is_already %}disabled class="text-muted"{% endif %}>{{ p.full_name }}{% if is_already %} (already registered){% endif %}</option>
{% endfor %}
</select>
<button type="submit" class="btn btn-sm btn-primary ml-2">
<i class="fas fa-plus"></i> Register Player
</button>
</form>
</div>
</div>
{% endif %}
<div class="dashboard-grid">
<div class="card">
<div class="card-header">
<h3><i class="fas fa-users"></i> Registered Players ({{ registered_players | length }})</h3>
</div>
<div class="card-body">
<div class="table-container">
<table class="table">
<thead>
<tr>
<th>Player</th>
<th>Status</th>
{% if current_user.can_evaluate() %}
<th>Evaluation</th>
<th>Actions</th>
{% endif %}
</tr>
</thead>
<tbody>
{% for p in registered_players %}
{% set reg = registrations | selectattr('player_id', 'equalto', p.id) | first %}
<tr>
<td>
<div class="user-mini">
<div class="avatar-sm">{{ p.full_name[:2] | upper }}</div>
<span>{{ p.full_name }}</span>
</div>
</td>
<td>
{% if current_user.can_manage_tryouts() %}
<form method="POST" action="{{ url_for('tryouts.update_registration_status', tryout_id=tryout.id, player_id=p.id) }}" class="inline-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<select name="status" onchange="this.form.submit()" class="form-select form-select-sm">
<option value="registered" {% if reg.status == 'registered' %}selected{% endif %}>Registered</option>
<option value="attended" {% if reg.status == 'attended' %}selected{% endif %}>Attended</option>
<option value="no_show" {% if reg.status == 'no_show' %}selected{% endif %}>No Show</option>
</select>
</form>
{% else %}
<span class="badge badge-{{ reg.status }}">{{ reg.status }}</span>
{% endif %}
</td>
{% if current_user.can_evaluate() %}
<td>
{% if player_eval_status.get(p.id) %}
<span class="badge badge-success">Evaluated</span>
{% else %}
<span class="badge badge-warning">Pending</span>
{% endif %}
</td>
<td>
<a href="{{ url_for('evaluations.evaluate_player', tryout_id=tryout.id, player_id=p.id) }}" class="btn btn-sm btn-primary">
<i class="fas fa-edit"></i> {% if player_eval_status.get(p.id) %}Edit{% else %}Evaluate{% endif %}
</a>
</td>
{% endif %}
</tr>
{% else %}
<tr>
<td colspan="{% if current_user.can_evaluate() %}4{% else %}2{% endif %}" class="text-center">
No players registered yet.
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
<div class="card">
<div class="card-header">
<h3><i class="fas fa-users-cog"></i> Teams</h3>
{% if current_user.can_manage_tryouts() %}
<button class="btn btn-sm btn-primary" onclick="showCreateTeam()">
<i class="fas fa-plus"></i> New Team
</button>
{% endif %}
</div>
<div class="card-body">
{% if current_user.can_manage_tryouts() %}
<div id="createTeamForm" class="hidden mb-3">
<form method="POST" action="{{ url_for('tryouts.create_team', tryout_id=tryout.id) }}" class="form-inline">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<input type="text" name="team_name" placeholder="Team name" required class="form-input mr-2">
<button type="submit" class="btn btn-sm btn-success">Create</button>
<button type="button" class="btn btn-sm btn-secondary" onclick="hideCreateTeam()">Cancel</button>
</form>
</div>
{% endif %}
{% for team in team_data %}
<div class="team-card mb-3">
<h4 class="team-name">{{ team.team.name }}</h4>
<ul class="team-members">
{% for member in team.members %}
<li>
<div class="avatar-sm">{{ member.player.full_name[:2] | upper }}</div>
<span>{{ member.player.full_name }}</span>
{% if member.position %}
<span class="position-tag">{{ member.position }}</span>
{% endif %}
</li>
{% else %}
<li class="text-muted">No players assigned yet.</li>
{% endfor %}
</ul>
{% if current_user.can_manage_tryouts() and registered_players %}
<form method="POST" action="{{ url_for('tryouts.add_to_team', tryout_id=tryout.id, team_id=team.team.id) }}" class="form-inline mt-2">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<select name="player_id" class="form-select" required>
<option value="">Select player...</option>
{% for p in registered_players %}
{% if p.id not in team.members | map(attribute='player') | map(attribute='id') | list %}
<option value="{{ p.id }}">{{ p.full_name }}</option>
{% endif %}
{% endfor %}
</select>
<input type="text" name="position" placeholder="Position" class="form-input mx-2">
<button type="submit" class="btn btn-sm btn-primary">Add</button>
</form>
{% endif %}
</div>
{% else %}
<p class="text-muted text-center">No teams created yet.</p>
{% endfor %}
</div>
</div>
</div>
<div class="card mt-4">
<div class="card-header">
<h3><i class="fas fa-star"></i> Evaluation Summary</h3>
</div>
<div class="card-body">
<div class="table-container">
<table class="table">
<thead>
<tr>
<th>Player</th>
<th>Evaluator</th>
<th>Speed</th>
<th>Agility</th>
<th>Technique</th>
<th>Teamwork</th>
<th>Attitude</th>
<th>Overall</th>
<th>Recommendation</th>
</tr>
</thead>
<tbody>
{% for eval in evaluations %}
<tr>
<td>{{ eval.player.full_name }}</td>
<td>{{ eval.evaluator.full_name }}</td>
<td>{{ eval.speed_score or '-' }}</td>
<td>{{ eval.agility_score or '-' }}</td>
<td>{{ eval.technique_score or '-' }}</td>
<td>{{ eval.teamwork_score or '-' }}</td>
<td>{{ eval.attitude_score or '-' }}</td>
<td><span class="score">{{ eval.overall_score or '-' }}</span></td>
<td>{{ eval.position_recommendation or '-' }}</td>
</tr>
{% else %}
<tr>
<td colspan="9" class="text-center">No evaluations yet.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
<script>
function showCreateTeam() { document.getElementById('createTeamForm').classList.remove('hidden'); }
function hideCreateTeam() { document.getElementById('createTeamForm').classList.add('hidden'); }
</script>
{% endblock %}