changement de l'affichage des tryouts et des matchs
This commit is contained in:
Binary file not shown.
@@ -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
@@ -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
|
||||||
|
|
||||||
|
|||||||
+58
-5
@@ -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'])
|
||||||
|
|||||||
+5
-3
@@ -30,7 +30,7 @@ def list_tryouts():
|
|||||||
- 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())
|
||||||
@@ -254,7 +256,7 @@ def view_tryout(tryout_id):
|
|||||||
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':
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
|||||||
@@ -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() %}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -12,50 +12,77 @@
|
|||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="card">
|
<div class="tryouts-grid">
|
||||||
<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 %}
|
{% for tryout in tryouts %}
|
||||||
<tr>
|
<div class="card tryout-card">
|
||||||
<td class="cell-title">{{ tryout.title }}</td>
|
<div class="card-header">
|
||||||
<td>{{ tryout.target_org_team.name if tryout.target_org_team else '-' }}</td>
|
<h3 class="tryout-title">{{ tryout.title }}</h3>
|
||||||
<td>{{ tryout.date.strftime('%b %d, %Y') }}</td>
|
<span class="badge badge-{{ tryout.status }}">{{ tryout.status | replace('_', ' ') | title }}</span>
|
||||||
<td>{{ tryout.location or 'TBD' }}</td>
|
</div>
|
||||||
<td>
|
<div class="card-body">
|
||||||
<span class="badge badge-{{ tryout.status }}">
|
<div class="tryout-info-grid">
|
||||||
{{ tryout.status | replace('_', ' ') | title }}
|
<div class="info-item">
|
||||||
</span>
|
<i class="fas fa-gamepad info-icon"></i>
|
||||||
</td>
|
<span class="info-label">Game</span>
|
||||||
<td>{{ tryout.registrations.count() }}</td>
|
<span class="info-value">{{ tryout.game }}</span>
|
||||||
<td>{{ tryout.evaluations.count() }}</td>
|
</div>
|
||||||
<td>
|
<div class="info-item">
|
||||||
|
<i class="fas fa-calendar info-icon"></i>
|
||||||
|
<span class="info-label">Date</span>
|
||||||
|
<span class="info-value">{{ tryout.date.strftime('%b %d, %Y') }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<i class="fas fa-map-marker-alt info-icon"></i>
|
||||||
|
<span class="info-label">Location</span>
|
||||||
|
<span class="info-value">{{ tryout.location or 'TBD' }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="info-item">
|
||||||
|
<i class="fas fa-users info-icon"></i>
|
||||||
|
<span class="info-label">Players</span>
|
||||||
|
<span class="info-value">{{ tryout.registrations.count() }}</span>
|
||||||
|
</div>
|
||||||
|
{% if current_user.can_evaluate() %}
|
||||||
|
<div class="info-item">
|
||||||
|
<i class="fas fa-clipboard-check info-icon"></i>
|
||||||
|
<span class="info-label">Evaluations</span>
|
||||||
|
<span class="info-value">{{ tryout.evaluations.count() }}</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if tryout.target_org_team %}
|
||||||
|
<div class="info-item">
|
||||||
|
<i class="fas fa-flag info-icon"></i>
|
||||||
|
<span class="info-label">Target Team</span>
|
||||||
|
<span class="info-value">{{ tryout.target_org_team.name }}</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
{% if tryout.max_players %}
|
||||||
|
<div class="info-item">
|
||||||
|
<i class="fas fa-user-friends info-icon"></i>
|
||||||
|
<span class="info-label">Max Players</span>
|
||||||
|
<span class="info-value">{{ tryout.max_players }}</span>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
{% if tryout.description %}
|
||||||
|
<div class="tryout-description mt-3">
|
||||||
|
<p>{{ tryout.description | truncate(120) }}</p>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
</div>
|
||||||
|
<div class="card-footer">
|
||||||
<a href="{{ url_for('tryouts.view_tryout', tryout_id=tryout.id) }}" class="btn btn-sm btn-outline">
|
<a href="{{ url_for('tryouts.view_tryout', tryout_id=tryout.id) }}" class="btn btn-sm btn-outline">
|
||||||
<i class="fas fa-eye"></i> View
|
<i class="fas fa-eye"></i> View Details
|
||||||
</a>
|
</a>
|
||||||
{% if current_user.can_evaluate() %}
|
{% if current_user.can_evaluate() %}
|
||||||
<a href="{{ url_for('evaluations.players_to_evaluate', tryout_id=tryout.id) }}" class="btn btn-sm btn-primary">
|
<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
|
<i class="fas fa-clipboard-check"></i> Evaluate
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
<tr>
|
<div class="card" style="grid-column: 1 / -1;">
|
||||||
<td colspan="8" class="text-center">
|
<div class="card-body text-center">
|
||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
<i class="fas fa-calendar-times"></i>
|
<i class="fas fa-calendar-times"></i>
|
||||||
<h3>No tryouts found</h3>
|
<h3>No tryouts found</h3>
|
||||||
@@ -64,12 +91,8 @@
|
|||||||
<a href="{{ url_for('tryouts.create_tryout') }}" class="btn btn-primary">Create Tryout</a>
|
<a href="{{ url_for('tryouts.create_tryout') }}" class="btn btn-primary">Create Tryout</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</div>
|
||||||
</tr>
|
</div>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -246,9 +246,10 @@
|
|||||||
{% 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 %}
|
||||||
@@ -263,20 +264,9 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<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 %}
|
{% if matches %}
|
||||||
<!-- Matches Table -->
|
<!-- Matches Table -->
|
||||||
<div class="table-container mt-3">
|
<div class="table-container">
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -388,10 +378,22 @@
|
|||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if matches %}
|
||||||
|
<!-- Mini Calendar -->
|
||||||
|
<div id="mini-calendar" style="min-height: 300px; margin-top: 20px;"></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 %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
{% if current_user.can_evaluate() %}
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3><i class="fas fa-star"></i> Evaluation Summary</h3>
|
<h3><i class="fas fa-star"></i> Evaluation Summary</h3>
|
||||||
@@ -443,7 +445,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
|||||||
Reference in New Issue
Block a user