changement de l'affichage des tryouts et des matchs

This commit is contained in:
cedrick2711
2026-07-22 22:19:58 -04:00
parent 346229778f
commit 5e909014f8
14 changed files with 573 additions and 326 deletions
Binary file not shown.
+2 -2
View File
@@ -88,7 +88,7 @@ def create_app():
response.headers['X-Content-Type-Options'] = 'nosniff' response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['X-Frame-Options'] = 'DENY' response.headers['X-Frame-Options'] = 'DENY'
response.headers['X-XSS-Protection'] = '1; mode=block' response.headers['X-XSS-Protection'] = '1; mode=block'
response.headers['Content-Security-Policy'] = "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com; font-src 'self' https://cdnjs.cloudflare.com; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none';" response.headers['Content-Security-Policy'] = "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net; style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com https://cdn.jsdelivr.net; font-src 'self' https://cdnjs.cloudflare.com; img-src 'self' data:; connect-src 'self'; frame-ancestors 'none';"
response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains' response.headers['Strict-Transport-Security'] = 'max-age=31536000; includeSubDomains'
return response return response
@@ -133,5 +133,5 @@ def create_app():
if __name__ == '__main__': if __name__ == '__main__':
app = create_app() app = create_app()
# Debug mode should only be enabled via environment variable for security # Debug mode should only be enabled via environment variable for security
debug_mode = os.environ.get('FLASK_DEBUG', 'false').lower() == 'true' debug_mode = os.getenv('FLASK_DEBUG', 'false').lower() == 'true'
app.run(debug=debug_mode, host='0.0.0.0', port=5000) app.run(debug=debug_mode, host='0.0.0.0', port=5000)
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -21,7 +21,7 @@ def is_safe_url(url):
""" """
if not url: if not url:
return False return False
parsed = url_parse(url) parsed = urlparse(url)
# Allow relative URLs (no netloc) or same-origin URLs # Allow relative URLs (no netloc) or same-origin URLs
return not parsed.netloc or parsed.netloc == request.host return not parsed.netloc or parsed.netloc == request.host
+59 -6
View File
@@ -6,8 +6,9 @@ This module handles player evaluation creation, management, and viewing.
from flask import Blueprint, render_template, redirect, url_for, flash, request from flask import Blueprint, render_template, redirect, url_for, flash, request
from flask_login import login_required, current_user from flask_login import login_required, current_user
from extensions import db from extensions import db
from models import User, Tryout, Evaluation, TryoutRegistration, GAME_POSITIONS from models import User, Tryout, Evaluation, TryoutRegistration, GAME_POSITIONS, OrgTeam
from sqlalchemy import func from sqlalchemy import func
from sqlalchemy.orm import aliased
evaluations_bp = Blueprint('evaluations', __name__, url_prefix='/evaluations') evaluations_bp = Blueprint('evaluations', __name__, url_prefix='/evaluations')
@@ -41,13 +42,55 @@ def list_evaluations():
Evaluators (coach/manager): Their given evaluations. Evaluators (coach/manager): Their given evaluations.
Players: Their received evaluations. Players: Their received evaluations.
Supports sorting by any column header via 'sort' and 'order' query parameters.
Returns: Returns:
Response: Rendered evaluations list template. Response: Rendered evaluations list template.
""" """
user = current_user user = current_user
# Get sort parameters
sort_column = request.args.get('sort', 'created_at')
sort_order = request.args.get('order', 'desc')
# Validate sort_order
if sort_order not in ('asc', 'desc'):
sort_order = 'desc'
# Map sort columns to SQLAlchemy expressions using aliased User models for relationship sorting
player_alias = aliased(User, name='eval_player')
evaluator_alias = aliased(User, name='eval_evaluator')
sort_map = {
'tryout': Tryout.title,
'player': player_alias.full_name,
'evaluator': evaluator_alias.full_name,
'mecanics_score': Evaluation.mecanics_score,
'cohesion_score': Evaluation.cohesion_score,
'communication_score': Evaluation.communication_score,
'gamesense_score': Evaluation.gamesense_score,
'versatility_score': Evaluation.versatility_score,
'discipline_score': Evaluation.discipline_score,
'analysis_score': Evaluation.analysis_score,
'sport_ethics_score': Evaluation.sport_ethics_score,
'mental_score': Evaluation.mental_score,
'overall_score': Evaluation.overall_score,
'position_recommendation': Evaluation.position_recommendation,
'created_at': Evaluation.created_at,
}
sort_expr = sort_map.get(sort_column, Evaluation.created_at)
if sort_order == 'asc':
sort_expr = sort_expr.asc()
else:
sort_expr = sort_expr.desc()
if user.role == 'president': if user.role == 'president':
evaluations = Evaluation.query.order_by(Evaluation.created_at.desc()).all() evaluations = Evaluation.query \
.outerjoin(Tryout, Evaluation.tryout_id == Tryout.id) \
.outerjoin(player_alias, Evaluation.player_id == player_alias.id) \
.outerjoin(evaluator_alias, Evaluation.evaluator_id == evaluator_alias.id) \
.order_by(sort_expr).all()
avg_scores = db.session.query( avg_scores = db.session.query(
Evaluation.player_id, Evaluation.player_id,
func.count(Evaluation.id).label('eval_count'), func.count(Evaluation.id).label('eval_count'),
@@ -60,13 +103,23 @@ def list_evaluations():
player_scores[p.id] = {'player': p, 'count': row.eval_count, 'avg': round(row.avg_score, 1) if row.avg_score else 0} 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(): elif user.can_evaluate():
evaluations = Evaluation.query.filter_by(evaluator_id=user.id).order_by(Evaluation.created_at.desc()).all() evaluations = Evaluation.query \
.outerjoin(Tryout, Evaluation.tryout_id == Tryout.id) \
.outerjoin(player_alias, Evaluation.player_id == player_alias.id) \
.outerjoin(evaluator_alias, Evaluation.evaluator_id == evaluator_alias.id) \
.filter(Evaluation.evaluator_id == user.id) \
.order_by(sort_expr).all()
player_scores = {} player_scores = {}
else: else:
evaluations = Evaluation.query.filter_by(player_id=user.id).order_by(Evaluation.created_at.desc()).all() evaluations = Evaluation.query \
.outerjoin(Tryout, Evaluation.tryout_id == Tryout.id) \
.outerjoin(player_alias, Evaluation.player_id == player_alias.id) \
.outerjoin(evaluator_alias, Evaluation.evaluator_id == evaluator_alias.id) \
.filter(Evaluation.player_id == user.id) \
.order_by(sort_expr).all()
player_scores = {} player_scores = {}
return render_template('pages/evaluations.html', evaluations=evaluations, player_scores=player_scores) return render_template('pages/evaluations.html', evaluations=evaluations, player_scores=player_scores, sort_column=sort_column, sort_order=sort_order)
@evaluations_bp.route('/<int:tryout_id>/<int:player_id>', methods=['GET', 'POST']) @evaluations_bp.route('/<int:tryout_id>/<int:player_id>', methods=['GET', 'POST'])
@@ -219,4 +272,4 @@ def players_to_evaluate(tryout_id):
).first() ).first()
players.append({'player': p, 'evaluated': existing is not None, 'registration': reg}) players.append({'player': p, 'evaluated': existing is not None, 'registration': reg})
return render_template('pages/players_to_evaluate.html', tryout=tryout, players=players) return render_template('pages/players_to_evaluate.html', tryout=tryout, players=players)
+41 -39
View File
@@ -14,7 +14,7 @@ tryouts_bp = Blueprint('tryouts', __name__, url_prefix='/tryouts')
def can_manage(): def can_manage():
"""Check if current user can manage tryouts. """Check if current user can manage tryouts.
Returns: Returns:
bool: True if user is president or manager. bool: True if user is president or manager.
""" """
@@ -25,13 +25,13 @@ def can_manage():
@login_required @login_required
def list_tryouts(): def list_tryouts():
"""List all tryouts visible to the current user. """List all tryouts visible to the current user.
Shows tryouts filtered by user's role: Shows tryouts filtered by user's role:
- President: All tryouts - President: All tryouts
- Manager: Only their created tryouts - Manager: Only their created tryouts
- Coach: Tryouts targeting their org team - Coach: Tryouts targeting their org team
- Player: Upcoming and in-progress tryouts - Player: Only tryouts they are registered for or participating in
Returns: Returns:
Response: Rendered tryouts list template. Response: Rendered tryouts list template.
""" """
@@ -47,7 +47,9 @@ def list_tryouts():
else: else:
tryouts = [] tryouts = []
elif current_user.role == 'player': elif current_user.role == 'player':
tryouts = Tryout.query.filter(Tryout.status.in_(['upcoming', 'in_progress'])).order_by(Tryout.date.desc()).all() # Players only see tryouts they are registered for or participating in matches
from routes.matches import get_visible_tryouts_for_user
tryouts = get_visible_tryouts_for_user()
else: else:
tryouts = Tryout.query.order_by(Tryout.date.desc()).all() tryouts = Tryout.query.order_by(Tryout.date.desc()).all()
return render_template('pages/tryouts.html', tryouts=tryouts, now=datetime.utcnow()) return render_template('pages/tryouts.html', tryouts=tryouts, now=datetime.utcnow())
@@ -57,12 +59,12 @@ def list_tryouts():
@login_required @login_required
def create_tryout(): def create_tryout():
"""Create a new tryout event. """Create a new tryout event.
GET: Render the tryout creation form. GET: Render the tryout creation form.
POST: Create a tryout with the submitted details. POST: Create a tryout with the submitted details.
Requires president or manager role. Requires president or manager role.
Returns: Returns:
Response: Create form or redirect to the new tryout. Response: Create form or redirect to the new tryout.
""" """
@@ -110,15 +112,15 @@ def create_tryout():
@login_required @login_required
def edit_tryout(tryout_id): def edit_tryout(tryout_id):
"""Edit an existing tryout event. """Edit an existing tryout event.
GET: Render the tryout edit form with current data. GET: Render the tryout edit form with current data.
POST: Update the tryout with submitted changes. POST: Update the tryout with submitted changes.
Permission based on can_manage_this_tryout check. Permission based on can_manage_this_tryout check.
Args: Args:
tryout_id: The ID of the tryout to edit. tryout_id: The ID of the tryout to edit.
Returns: Returns:
Response: Edit form or redirect to tryout view. Response: Edit form or redirect to tryout view.
""" """
@@ -164,18 +166,18 @@ def edit_tryout(tryout_id):
@login_required @login_required
def view_tryout(tryout_id): def view_tryout(tryout_id):
"""View a specific tryout with all details. """View a specific tryout with all details.
Displays tryout information, registered players, evaluations, teams, Displays tryout information, registered players, evaluations, teams,
matches, and evaluation status information. matches, and evaluation status information.
Args: Args:
tryout_id: The ID of the tryout to view. tryout_id: The ID of the tryout to view.
Returns: Returns:
Response: Rendered tryout detail template. Response: Rendered tryout detail template.
""" """
tryout = Tryout.query.get_or_404(tryout_id) tryout = Tryout.query.get_or_404(tryout_id)
# Check if user has permission to view this tryout # Check if user has permission to view this tryout
can_view = False can_view = False
if current_user.role == 'president': if current_user.role == 'president':
@@ -197,11 +199,11 @@ def view_tryout(tryout_id):
can_view = is_registered or player_in_match can_view = is_registered or player_in_match
elif current_user.role == 'scout': elif current_user.role == 'scout':
can_view = True can_view = True
if not can_view: if not can_view:
flash('You do not have permission to view this tryout.', 'danger') flash('You do not have permission to view this tryout.', 'danger')
return redirect(url_for('tryouts.list_tryouts')) return redirect(url_for('tryouts.list_tryouts'))
registrations = TryoutRegistration.query.filter_by(tryout_id=tryout_id).all() 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] 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() evaluations = Evaluation.query.filter_by(tryout_id=tryout_id).all()
@@ -235,7 +237,7 @@ def view_tryout(tryout_id):
# Determine if current user can edit this tryout # Determine if current user can edit this tryout
can_edit = current_user.can_manage_this_tryout(tryout) can_edit = current_user.can_manage_this_tryout(tryout)
# Determine if current user can view the calendar (managers/coaches can always see it) # Determine if current user can view the calendar (managers/coaches can always see it)
# Players need to be registered or participating in a match # Players need to be registered or participating in a match
can_view_calendar = can_edit can_view_calendar = can_edit
@@ -245,16 +247,16 @@ def view_tryout(tryout_id):
MatchParticipant.player_id == current_user.id, MatchParticipant.player_id == current_user.id,
Match.tryout_id == tryout_id Match.tryout_id == tryout_id
).first() is not None ).first() is not None
can_view_calendar = is_registered or player_in_match can_view_calendar = is_registered or player_in_match
# Only expose all_players to users who can manage players in this tryout # Only expose all_players to users who can manage players in this tryout
all_players = None all_players = None
if can_edit: if can_edit:
all_players = User.query.filter_by(role='player').order_by(User.full_name).all() all_players = User.query.filter_by(role='player').order_by(User.full_name).all()
# Get matches for this tryout with participant info # Get matches for this tryout with participant info
matches = Match.query.filter_by(tryout_id=tryout_id).order_by(Match.date).all() matches = Match.query.filter_by(tryout_id=tryout_id).order_by(Match.date, Match.start_time).all()
match_data = [] match_data = []
for match in matches: for match in matches:
if match.match_type == 'team_vs_team': if match.match_type == 'team_vs_team':
@@ -279,7 +281,7 @@ def view_tryout(tryout_id):
'match': match, 'match': match,
'participants': participants 'participants': participants
}) })
return render_template('pages/view_tryout.html', return render_template('pages/view_tryout.html',
tryout=tryout, tryout=tryout,
registered_players=registered_players, registered_players=registered_players,
@@ -301,13 +303,13 @@ def view_tryout(tryout_id):
@login_required @login_required
def register_for_tryout(tryout_id): def register_for_tryout(tryout_id):
"""Register a player for a tryout. """Register a player for a tryout.
Allows players to register for tryouts. Validates that the tryout Allows players to register for tryouts. Validates that the tryout
is accepting registrations and not at capacity. is accepting registrations and not at capacity.
Args: Args:
tryout_id: The ID of the tryout to register for. tryout_id: The ID of the tryout to register for.
Returns: Returns:
Response: Redirect to tryout view with status message. Response: Redirect to tryout view with status message.
""" """
@@ -342,12 +344,12 @@ def register_for_tryout(tryout_id):
@login_required @login_required
def update_status(tryout_id): def update_status(tryout_id):
"""Update the status of a tryout. """Update the status of a tryout.
Changes tryout status between upcoming, in_progress, and completed. Changes tryout status between upcoming, in_progress, and completed.
Args: Args:
tryout_id: The ID of the tryout to update. tryout_id: The ID of the tryout to update.
Returns: Returns:
Response: Redirect to tryout view. Response: Redirect to tryout view.
""" """
@@ -367,11 +369,11 @@ def update_status(tryout_id):
@login_required @login_required
def update_registration_status(tryout_id, player_id): def update_registration_status(tryout_id, player_id):
"""Update the attendance status of a tryout registration. """Update the attendance status of a tryout registration.
Args: Args:
tryout_id: The ID of the tryout. tryout_id: The ID of the tryout.
player_id: The ID of the player whose status to update. player_id: The ID of the player whose status to update.
Returns: Returns:
Response: Redirect to tryout view. Response: Redirect to tryout view.
""" """
@@ -393,12 +395,12 @@ def update_registration_status(tryout_id, player_id):
@login_required @login_required
def register_player(tryout_id): def register_player(tryout_id):
"""Manually register a player for a tryout (by managers/coaches). """Manually register a player for a tryout (by managers/coaches).
Allows authorized users to register players on their behalf. Allows authorized users to register players on their behalf.
Args: Args:
tryout_id: The ID of the tryout. tryout_id: The ID of the tryout.
Returns: Returns:
Response: Redirect to tryout view with status message. Response: Redirect to tryout view with status message.
""" """
@@ -439,10 +441,10 @@ def register_player(tryout_id):
@login_required @login_required
def create_team(tryout_id): def create_team(tryout_id):
"""Create a tryout-specific team. """Create a tryout-specific team.
Args: Args:
tryout_id: The ID of the tryout to create the team for. tryout_id: The ID of the tryout to create the team for.
Returns: Returns:
Response: Redirect to tryout view with status message. Response: Redirect to tryout view with status message.
""" """
@@ -464,11 +466,11 @@ def create_team(tryout_id):
@login_required @login_required
def add_to_team(tryout_id, team_id): def add_to_team(tryout_id, team_id):
"""Add a player to a tryout team. """Add a player to a tryout team.
Args: Args:
tryout_id: The ID of the tryout. tryout_id: The ID of the tryout.
team_id: The ID of the team to add the player to. team_id: The ID of the team to add the player to.
Returns: Returns:
Response: Redirect to tryout view with status message. Response: Redirect to tryout view with status message.
""" """
@@ -490,4 +492,4 @@ def add_to_team(tryout_id, team_id):
db.session.commit() db.session.commit()
flash('Player added to team!', 'success') flash('Player added to team!', 'success')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id)) return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
+144
View File
@@ -1749,3 +1749,147 @@ a:hover { color: var(--primary-dark); }
transform: translateY(-50%); transform: translateY(-50%);
margin: 0; margin: 0;
} }
/* ===== Tryouts Grid (Card-based Layout) ===== */
.tryouts-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(400px, 1fr));
gap: 20px;
margin-bottom: 20px;
}
.tryout-card {
transition: transform 0.2s ease, box-shadow 0.2s ease;
border: 1px solid var(--border-color, #e0e0e0);
}
.tryout-card:hover {
transform: translateY(-2px);
box-shadow: var(--shadow-md);
}
.tryout-card .card-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12px;
}
.tryout-title {
font-size: 1.1rem;
font-weight: 600;
margin: 0;
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.tryout-info-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
.info-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 0.85rem;
}
.info-icon {
width: 16px;
text-align: center;
color: var(--text-muted, #888);
flex-shrink: 0;
}
.info-label {
font-weight: 600;
color: var(--text-secondary, #555);
min-width: 50px;
}
.info-value {
color: var(--text-primary, #333);
flex: 1;
text-align: right;
}
.tryout-description {
font-size: 0.85rem;
color: var(--text-secondary, #555);
line-height: 1.4;
}
.tryout-description p {
margin: 0;
}
.tryout-card .card-footer {
display: flex;
gap: 8px;
padding: 12px 16px;
background: var(--bg-secondary, #f8f9fa);
border-top: 1px solid var(--border-color, #e0e0e0);
}
/* ===== Full-width Schedule Card ===== */
.tryout-schedule-card {
width: 100%;
}
/* ===== Dark mode adjustments ===== */
[data-theme="dark"] .tryout-card {
border-color: var(--border-color, #333);
}
[data-theme="dark"] .tryout-card .card-footer {
background: var(--bg-secondary, #2a2a2a);
border-top-color: var(--border-color, #333);
}
[data-theme="dark"] .info-label {
color: var(--text-secondary, #aaa);
}
/* Sortable Table Headers */
.sort-link {
display: inline-flex;
align-items: center;
gap: 4px;
color: var(--gray-500);
font-weight: 600;
text-decoration: none;
transition: var(--transition);
}
.sort-link:hover {
color: var(--gray-800);
}
.sort-link .fas {
font-size: 0.75rem;
}
.sort-inactive {
opacity: 0.3;
font-size: 0.75rem;
}
.sort-link:hover .sort-inactive {
opacity: 0.5;
}
[data-theme="dark"] .sort-link {
color: var(--text-secondary);
}
[data-theme="dark"] .sort-link:hover {
color: var(--text-primary);
}
+37 -20
View File
@@ -4,6 +4,23 @@
{% block breadcrumb %}<span class="breadcrumb">Home / Evaluations</span>{% endblock %} {% block breadcrumb %}<span class="breadcrumb">Home / Evaluations</span>{% endblock %}
{% block content %} {% block content %}
{% set sort_column = sort_column | default('created_at') %}
{% set sort_order = sort_order | default('desc') %}
{% macro sort_link(column, label) %}
<th>
{% set new_order = 'asc' if (sort_column == column and sort_order == 'desc') else 'desc' %}
<a href="{{ url_for('evaluations.list_evaluations', sort=column, order=new_order) }}" class="sort-link">
{{ label }}
{% if sort_column == column %}
<i class="fas fa-arrow-{{ 'up' if sort_order == 'asc' else 'down' }}"></i>
{% else %}
<i class="fas fa-arrows-alt-v sort-inactive"></i>
{% endif %}
</a>
</th>
{% endmacro %}
{% if current_user.role == 'president' and player_scores %} {% if current_user.role == 'president' and player_scores %}
<div class="stats-grid mb-4"> <div class="stats-grid mb-4">
{% for pid, data in player_scores.items() %} {% for pid, data in player_scores.items() %}
@@ -22,7 +39,7 @@
<div class="card"> <div class="card">
<div class="card-header"> <div class="card-header">
<h3><i class="fas fa-clipboard-list"></i> <h3><i class="fas fa-clipboard-list"></i>
{% if current_user.role == 'player' %} {% if current_user.role == 'player' %}
My Evaluations My Evaluations
{% else %} {% else %}
@@ -35,29 +52,29 @@
<table class="table"> <table class="table">
<thead> <thead>
<tr> <tr>
<th>Tryout</th> {{ sort_link('tryout', 'Tryout') }}
<th>Player</th> {{ sort_link('player', 'Player') }}
<th>Evaluator</th> {{ sort_link('evaluator', 'Evaluator') }}
<th>Mecanics</th> {{ sort_link('mecanics_score', 'Mecanics') }}
<th>Cohesion</th> {{ sort_link('cohesion_score', 'Cohesion') }}
<th>Communication</th> {{ sort_link('communication_score', 'Communication') }}
<th>Gamesense</th> {{ sort_link('gamesense_score', 'Gamesense') }}
<th>Versatility</th> {{ sort_link('versatility_score', 'Versatility') }}
<th>Discipline</th> {{ sort_link('discipline_score', 'Discipline') }}
<th>Analysis</th> {{ sort_link('analysis_score', 'Analysis') }}
<th>Sport Ethics</th> {{ sort_link('sport_ethics_score', 'Sport Ethics') }}
<th>Mental</th> {{ sort_link('mental_score', 'Mental') }}
<th>Overall</th> {{ sort_link('overall_score', 'Overall') }}
<th>Position</th> {{ sort_link('position_recommendation', 'Position') }}
<th>Date</th> {{ sort_link('created_at', 'Date') }}
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for eval in evaluations %} {% for eval in evaluations %}
<tr> <tr>
<td>{{ eval.tryout.title }}</td> <td>{{ eval.tryout.title if eval.tryout else 'Deleted Tryout' }}</td>
<td>{{ eval.player.full_name }}</td> <td>{{ eval.player.full_name if eval.player else 'Deleted Player' }}</td>
<td>{{ eval.evaluator.full_name }}</td> <td>{{ eval.evaluator.full_name if eval.evaluator else 'Deleted Evaluator' }}</td>
<td>{{ eval.mecanics_score or '-' }}</td> <td>{{ eval.mecanics_score or '-' }}</td>
<td>{{ eval.cohesion_score or '-' }}</td> <td>{{ eval.cohesion_score or '-' }}</td>
<td>{{ eval.communication_score or '-' }}</td> <td>{{ eval.communication_score or '-' }}</td>
@@ -86,4 +103,4 @@
</div> </div>
</div> </div>
</div> </div>
{% endblock %} {% endblock %}
+14 -8
View File
@@ -77,22 +77,28 @@
</span> </span>
</div> </div>
<div class="detail-item"> <div class="detail-item">
<span class="detail-label"><i class="fas fa-chart-line"></i> TRN (Tracker Network)</span> <span class="detail-label"><i class="fas fa-chart-line"></i> Games & TRN</span>
<span class="detail-value"> <span class="detail-value">
{% if user.gamertags %} {% set games_list = user.get_games_list() %}
{% if games_list %}
<div style="display: flex; flex-direction: column; gap: 8px;"> <div style="display: flex; flex-direction: column; gap: 8px;">
{% for gamertag in user.gamertags %} {% for game in games_list %}
{% set gt = user.gamertags | selectattr('game', 'equalto', game) | first %}
<div> <div>
<span class="badge badge-esport" style="margin-right: 8px;">{{ gamertag.game }}</span> {% if gt and gt.get_trn_url() %}
<a href="{{ gamertag.get_trn_url() }}" target="_blank" rel="noopener noreferrer" class="trn-link"> <a href="{{ gt.get_trn_url() }}" target="_blank" rel="noopener noreferrer" class="trn-link">
<i class="fas fa-external-link-alt"></i> {{ gamertag.gamertag }} <span class="badge badge-esport" style="margin-right: 8px;">{{ game }}</span>
{% if gamertag.platform %}<small>({{ gamertag.platform }})</small>{% endif %} <i class="fas fa-external-link-alt"></i> {{ gt.gamertag }}
{% if gt.platform %}<small>({{ gt.platform }})</small>{% endif %}
</a> </a>
{% else %}
<span class="badge badge-esport">{{ game }}</span>
{% endif %}
</div> </div>
{% endfor %} {% endfor %}
</div> </div>
{% else %} {% else %}
<span class="text-muted">Not connected</span> <span class="text-muted">Not specified</span>
{% endif %} {% endif %}
</span> </span>
</div> </div>
+81 -58
View File
@@ -12,64 +12,87 @@
{% endblock %} {% endblock %}
{% block content %} {% block content %}
<div class="card"> <div class="tryouts-grid">
<div class="card-body"> {% for tryout in tryouts %}
<div class="table-container"> <div class="card tryout-card">
<table class="table"> <div class="card-header">
<thead> <h3 class="tryout-title">{{ tryout.title }}</h3>
<tr> <span class="badge badge-{{ tryout.status }}">{{ tryout.status | replace('_', ' ') | title }}</span>
<th>Title</th> </div>
<th>Target Team</th> <div class="card-body">
<th>Date</th> <div class="tryout-info-grid">
<th>Location</th> <div class="info-item">
<th>Status</th> <i class="fas fa-gamepad info-icon"></i>
<th>Players</th> <span class="info-label">Game</span>
<th>Evaluations</th> <span class="info-value">{{ tryout.game }}</span>
<th>Actions</th> </div>
</tr> <div class="info-item">
</thead> <i class="fas fa-calendar info-icon"></i>
<tbody> <span class="info-label">Date</span>
{% for tryout in tryouts %} <span class="info-value">{{ tryout.date.strftime('%b %d, %Y') }}</span>
<tr> </div>
<td class="cell-title">{{ tryout.title }}</td> <div class="info-item">
<td>{{ tryout.target_org_team.name if tryout.target_org_team else '-' }}</td> <i class="fas fa-map-marker-alt info-icon"></i>
<td>{{ tryout.date.strftime('%b %d, %Y') }}</td> <span class="info-label">Location</span>
<td>{{ tryout.location or 'TBD' }}</td> <span class="info-value">{{ tryout.location or 'TBD' }}</span>
<td> </div>
<span class="badge badge-{{ tryout.status }}"> <div class="info-item">
{{ tryout.status | replace('_', ' ') | title }} <i class="fas fa-users info-icon"></i>
</span> <span class="info-label">Players</span>
</td> <span class="info-value">{{ tryout.registrations.count() }}</span>
<td>{{ tryout.registrations.count() }}</td> </div>
<td>{{ tryout.evaluations.count() }}</td> {% if current_user.can_evaluate() %}
<td> <div class="info-item">
<a href="{{ url_for('tryouts.view_tryout', tryout_id=tryout.id) }}" class="btn btn-sm btn-outline"> <i class="fas fa-clipboard-check info-icon"></i>
<i class="fas fa-eye"></i> View <span class="info-label">Evaluations</span>
</a> <span class="info-value">{{ tryout.evaluations.count() }}</span>
{% if current_user.can_evaluate() %} </div>
<a href="{{ url_for('evaluations.players_to_evaluate', tryout_id=tryout.id) }}" class="btn btn-sm btn-primary"> {% endif %}
<i class="fas fa-clipboard-check"></i> Evaluate {% if tryout.target_org_team %}
</a> <div class="info-item">
{% endif %} <i class="fas fa-flag info-icon"></i>
</td> <span class="info-label">Target Team</span>
</tr> <span class="info-value">{{ tryout.target_org_team.name }}</span>
{% else %} </div>
<tr> {% endif %}
<td colspan="8" class="text-center"> {% if tryout.max_players %}
<div class="empty-state"> <div class="info-item">
<i class="fas fa-calendar-times"></i> <i class="fas fa-user-friends info-icon"></i>
<h3>No tryouts found</h3> <span class="info-label">Max Players</span>
{% if current_user.can_manage_tryouts() %} <span class="info-value">{{ tryout.max_players }}</span>
<p>Get started by creating a new tryout.</p> </div>
<a href="{{ url_for('tryouts.create_tryout') }}" class="btn btn-primary">Create Tryout</a> {% endif %}
{% endif %} </div>
</div> {% if tryout.description %}
</td> <div class="tryout-description mt-3">
</tr> <p>{{ tryout.description | truncate(120) }}</p>
{% endfor %} </div>
</tbody> {% endif %}
</table> </div>
<div class="card-footer">
<a href="{{ url_for('tryouts.view_tryout', tryout_id=tryout.id) }}" class="btn btn-sm btn-outline">
<i class="fas fa-eye"></i> View Details
</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 %}
</div> </div>
</div> </div>
{% else %}
<div class="card" style="grid-column: 1 / -1;">
<div class="card-body 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>
</div>
</div>
{% endfor %}
</div> </div>
{% endblock %} {% endblock %}
+194 -192
View File
@@ -246,204 +246,206 @@
{% endfor %} {% endfor %}
</div> </div>
</div> </div>
</div>
{% if can_view_calendar %} {% if can_view_calendar %}
<div class="card"> <div class="card tryout-schedule-card mb-4">
<div class="card-header"> <div class="card-header">
<h3><i class="fas fa-futbol"></i> Schedule</h3> <h3><i class="fas fa-futbol"></i> Schedule</h3>
{% if can_edit %} {% if can_edit %}
<div class="card-actions"> <div class="card-actions">
<a href="{{ url_for('matches.create_match', tryout_id=tryout.id) }}" class="btn btn-sm btn-success"> <a href="{{ url_for('matches.create_match', tryout_id=tryout.id) }}" class="btn btn-sm btn-success">
<i class="fas fa-plus"></i> Schedule Match <i class="fas fa-plus"></i> Schedule Match
</a> </a>
<a href="{{ url_for('users.add_note_from_tryout', tryout_id=tryout.id) }}" class="btn btn-sm btn-primary"> <a href="{{ url_for('users.add_note_from_tryout', tryout_id=tryout.id) }}" class="btn btn-sm btn-primary">
<i class="fas fa-sticky-note"></i> Add Note <i class="fas fa-sticky-note"></i> Add Note
</a> </a>
</div>
{% endif %}
</div>
<div class="card-body">
{% if matches %}
<!-- Mini Calendar -->
<div id="mini-calendar" style="min-height: 300px;"></div>
{% else %}
<!-- No matches yet - show message -->
<div class="text-center py-4">
<i class="fas fa-calendar-alt fa-3x text-muted mb-3"></i>
<p class="text-muted">No matches scheduled yet. Check back later!</p>
</div>
{% endif %}
{% if matches %}
<!-- Matches Table -->
<div class="table-container mt-3">
<table class="table">
<thead>
<tr>
<th>Match</th>
<th>Type</th>
<th>Date</th>
<th>Participants</th>
<th>Time</th>
<th>Status</th>
{% if can_edit %}
<th>Actions</th>
{% endif %}
</tr>
</thead>
<tbody>
{% for item in match_data %}
{% set m = item.match %}
{% set participants = item.participants %}
<tr>
<td class="cell-title">{{ m.title }}</td>
<td>
<span class="badge badge-{{ 'success' if m.match_type in ['team_vs_team', 'player_vs_player'] else 'warning' }}">
{% if m.match_type == 'team_vs_team' %}
Team vs Team
{% elif m.match_type == 'player_vs_player' %}
Player vs Player
{% else %}
Player Scrim
{% endif %}
</span>
</td>
<td>{{ m.date.strftime('%m/%d/%Y') }}</td>
<td>
{% if m.match_type == 'team_vs_team' %}
<div class="match-teams">
<div class="match-team">
<span class="team-name">{{ m.team1.name if m.team1 else 'Team 1' }}</span>
{% if participants.team1_players %}
<ul class="team-players-list">
{% for pl in participants.team1_players %}
<li>{{ pl.name }}{% if pl.position %} <span class="position-tag">{{ pl.position }}</span>{% endif %}</li>
{% endfor %}
</ul>
{% endif %}
</div>
<div class="match-vs">vs</div>
<div class="match-team">
<span class="team-name">{{ m.team2.name if m.team2 else 'Team 2' }}</span>
{% if participants.team2_players %}
<ul class="team-players-list">
{% for pl in participants.team2_players %}
<li>{{ pl.name }}{% if pl.position %} <span class="position-tag">{{ pl.position }}</span>{% endif %}</li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
{% elif m.match_type == 'player_vs_player' %}
<div class="match-teams">
<div class="match-team">
<span class="team-name">Team 1</span>
{% if participants.team1_players %}
<ul class="team-players-list">
{% for pl in participants.team1_players %}
<li>{{ pl.name }}{% if pl.position %} <span class="position-tag">{{ pl.position }}</span>{% endif %}</li>
{% endfor %}
</ul>
{% endif %}
</div>
<div class="match-vs">vs</div>
<div class="match-team">
<span class="team-name">Team 2</span>
{% if participants.team2_players %}
<ul class="team-players-list">
{% for pl in participants.team2_players %}
<li>{{ pl.name }}{% if pl.position %} <span class="position-tag">{{ pl.position }}</span>{% endif %}</li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
{% else %}
{{ participants | join(', ') }}
{% endif %}
</td>
<td>
{% if m.start_time and m.end_time %}
{{ m.start_time.strftime('%H:%M') }} - {{ m.end_time.strftime('%H:%M') }}
{% else %}
TBD
{% endif %}
</td>
<td>
<span class="badge badge-{{ m.status }}">{{ m.status }}</span>
</td>
{% if can_edit %}
<td>
<a href="{{ url_for('matches.edit_match', match_id=m.id) }}" class="btn btn-sm btn-outline">
<i class="fas fa-edit"></i> Edit
</a>
<a href="{{ url_for('users.add_note_from_match', match_id=m.id) }}" class="btn btn-sm btn-primary" title="Add Note for this Match">
<i class="fas fa-sticky-note"></i> Note
</a>
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
</div> </div>
{% endif %}
</div> </div>
{% endif %} <div class="card-body">
{% if matches %}
<div class="card"> <!-- Matches Table -->
<div class="card-header"> <div class="table-container">
<h3><i class="fas fa-star"></i> Evaluation Summary</h3> <table class="table">
<thead>
<tr>
<th>Match</th>
<th>Type</th>
<th>Date</th>
<th>Participants</th>
<th>Time</th>
<th>Status</th>
{% if can_edit %}
<th>Actions</th>
{% endif %}
</tr>
</thead>
<tbody>
{% for item in match_data %}
{% set m = item.match %}
{% set participants = item.participants %}
<tr>
<td class="cell-title">{{ m.title }}</td>
<td>
<span class="badge badge-{{ 'success' if m.match_type in ['team_vs_team', 'player_vs_player'] else 'warning' }}">
{% if m.match_type == 'team_vs_team' %}
Team vs Team
{% elif m.match_type == 'player_vs_player' %}
Player vs Player
{% else %}
Player Scrim
{% endif %}
</span>
</td>
<td>{{ m.date.strftime('%m/%d/%Y') }}</td>
<td>
{% if m.match_type == 'team_vs_team' %}
<div class="match-teams">
<div class="match-team">
<span class="team-name">{{ m.team1.name if m.team1 else 'Team 1' }}</span>
{% if participants.team1_players %}
<ul class="team-players-list">
{% for pl in participants.team1_players %}
<li>{{ pl.name }}{% if pl.position %} <span class="position-tag">{{ pl.position }}</span>{% endif %}</li>
{% endfor %}
</ul>
{% endif %}
</div>
<div class="match-vs">vs</div>
<div class="match-team">
<span class="team-name">{{ m.team2.name if m.team2 else 'Team 2' }}</span>
{% if participants.team2_players %}
<ul class="team-players-list">
{% for pl in participants.team2_players %}
<li>{{ pl.name }}{% if pl.position %} <span class="position-tag">{{ pl.position }}</span>{% endif %}</li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
{% elif m.match_type == 'player_vs_player' %}
<div class="match-teams">
<div class="match-team">
<span class="team-name">Team 1</span>
{% if participants.team1_players %}
<ul class="team-players-list">
{% for pl in participants.team1_players %}
<li>{{ pl.name }}{% if pl.position %} <span class="position-tag">{{ pl.position }}</span>{% endif %}</li>
{% endfor %}
</ul>
{% endif %}
</div>
<div class="match-vs">vs</div>
<div class="match-team">
<span class="team-name">Team 2</span>
{% if participants.team2_players %}
<ul class="team-players-list">
{% for pl in participants.team2_players %}
<li>{{ pl.name }}{% if pl.position %} <span class="position-tag">{{ pl.position }}</span>{% endif %}</li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
{% else %}
{{ participants | join(', ') }}
{% endif %}
</td>
<td>
{% if m.start_time and m.end_time %}
{{ m.start_time.strftime('%H:%M') }} - {{ m.end_time.strftime('%H:%M') }}
{% else %}
TBD
{% endif %}
</td>
<td>
<span class="badge badge-{{ m.status }}">{{ m.status }}</span>
</td>
{% if can_edit %}
<td>
<a href="{{ url_for('matches.edit_match', match_id=m.id) }}" class="btn btn-sm btn-outline">
<i class="fas fa-edit"></i> Edit
</a>
<a href="{{ url_for('users.add_note_from_match', match_id=m.id) }}" class="btn btn-sm btn-primary" title="Add Note for this Match">
<i class="fas fa-sticky-note"></i> Note
</a>
</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div> </div>
<div class="card-body"> {% endif %}
<div class="table-container">
<table class="table"> {% if matches %}
<thead> <!-- Mini Calendar -->
<tr> <div id="mini-calendar" style="min-height: 300px; margin-top: 20px;"></div>
<th>Player</th> {% else %}
<th>Evaluator</th> <!-- No matches yet - show message -->
<th>Mecanics</th> <div class="text-center py-4">
<th>Cohesion</th> <i class="fas fa-calendar-alt fa-3x text-muted mb-3"></i>
<th>Communication</th> <p class="text-muted">No matches scheduled yet. Check back later!</p>
<th>Gamesense</th> </div>
<th>Versatility</th> {% endif %}
<th>Discipline</th> </div>
<th>Analysis</th> </div>
<th>Sport Ethics</th> {% endif %}
<th>Mental</th>
<th>Overall</th> {% if current_user.can_evaluate() %}
<th>Recommendation</th> <div class="card">
</tr> <div class="card-header">
</thead> <h3><i class="fas fa-star"></i> Evaluation Summary</h3>
<tbody> </div>
{% for eval in evaluations %} <div class="card-body">
<tr> <div class="table-container">
<td>{{ eval.player.full_name }}</td> <table class="table">
<td>{{ eval.evaluator.full_name }}</td> <thead>
<td>{{ eval.mecanics_score or '-' }}</td> <tr>
<td>{{ eval.cohesion_score or '-' }}</td> <th>Player</th>
<td>{{ eval.communication_score or '-' }}</td> <th>Evaluator</th>
<td>{{ eval.gamesense_score or '-' }}</td> <th>Mecanics</th>
<td>{{ eval.versatility_score or '-' }}</td> <th>Cohesion</th>
<td>{{ eval.discipline_score or '-' }}</td> <th>Communication</th>
<td>{{ eval.analysis_score or '-' }}</td> <th>Gamesense</th>
<td>{{ eval.sport_ethics_score or '-' }}</td> <th>Versatility</th>
<td>{{ eval.mental_score or '-' }}</td> <th>Discipline</th>
<td><span class="score">{{ eval.overall_score or '-' }}</span></td> <th>Analysis</th>
<td>{{ eval.position_recommendation or '-' }}</td> <th>Sport Ethics</th>
</tr> <th>Mental</th>
{% else %} <th>Overall</th>
<tr> <th>Recommendation</th>
<td colspan="13" class="text-center">No evaluations yet.</td> </tr>
</tr> </thead>
{% endfor %} <tbody>
</tbody> {% for eval in evaluations %}
</table> <tr>
</div> <td>{{ eval.player.full_name }}</td>
<td>{{ eval.evaluator.full_name }}</td>
<td>{{ eval.mecanics_score or '-' }}</td>
<td>{{ eval.cohesion_score or '-' }}</td>
<td>{{ eval.communication_score or '-' }}</td>
<td>{{ eval.gamesense_score or '-' }}</td>
<td>{{ eval.versatility_score or '-' }}</td>
<td>{{ eval.discipline_score or '-' }}</td>
<td>{{ eval.analysis_score or '-' }}</td>
<td>{{ eval.sport_ethics_score or '-' }}</td>
<td>{{ eval.mental_score or '-' }}</td>
<td><span class="score">{{ eval.overall_score or '-' }}</span></td>
<td>{{ eval.position_recommendation or '-' }}</td>
</tr>
{% else %}
<tr>
<td colspan="13" class="text-center">No evaluations yet.</td>
</tr>
{% endfor %}
</tbody>
</table>
</div> </div>
</div> </div>
</div> </div>
{% endif %}
</div> </div>
<script> <script>
@@ -473,4 +475,4 @@ document.addEventListener('DOMContentLoaded', function() {
} }
}); });
</script> </script>
{% endblock %} {% endblock %}