This commit is contained in:
cedrick2711
2026-08-25 14:03:07 -04:00
191 changed files with 28790 additions and 5066 deletions
+6 -6
View File
@@ -1,15 +1,15 @@
{% extends "layouts/base.html" %}
{% block title %}400 Bad Request - TryoutPro{% endblock %}
{% block page_title %}Bad Request{% endblock %}
{% block title %}{{ _('400 Bad Request') }} - UdeS team manager{% endblock %}
{% block page_title %}{{ _('Bad Request') }}{% endblock %}
{% block content %}
<div class="error-container">
<div class="error-icon">
<i class="fas fa-exclamation-triangle"></i>
</div>
<h2>400 — Bad Request</h2>
<p>The request could not be understood by the server. Please check your input and try again.</p>
<h2>{{ _('400 — Bad Request') }}</h2>
<p>{{ _('The request could not be understood by the server. Please check your input and try again.') }}</p>
<a href="{{ url_for('main.dashboard') if current_user.is_authenticated else url_for('auth.login') }}" class="btn btn-primary">
<i class="fas fa-arrow-left"></i> Go Back
<i class="fas fa-arrow-left"></i> {{ _('Go Back') }}
</a>
</div>
{% endblock %}
{% endblock %}
+6 -6
View File
@@ -1,15 +1,15 @@
{% extends "layouts/base.html" %}
{% block title %}403 Forbidden - TryoutPro{% endblock %}
{% block page_title %}Access Denied{% endblock %}
{% block title %}{{ _('403 Forbidden') }} - UdeS team manager{% endblock %}
{% block page_title %}{{ _('Access Denied') }}{% endblock %}
{% block content %}
<div class="error-container">
<div class="error-icon">
<i class="fas fa-lock"></i>
</div>
<h2>403 — Forbidden</h2>
<p>You do not have permission to access this resource. If you believe this is an error, please contact an administrator.</p>
<h2>{{ _('403 — Forbidden') }}</h2>
<p>{{ _('You do not have permission to access this resource. If you believe this is an error, please contact an administrator.') }}</p>
<a href="{{ url_for('main.dashboard') if current_user.is_authenticated else url_for('auth.login') }}" class="btn btn-primary">
<i class="fas fa-arrow-left"></i> Go Back
<i class="fas fa-arrow-left"></i> {{ _('Go Back') }}
</a>
</div>
{% endblock %}
{% endblock %}
+6 -6
View File
@@ -1,15 +1,15 @@
{% extends "layouts/base.html" %}
{% block title %}404 Not Found - TryoutPro{% endblock %}
{% block page_title %}Page Not Found{% endblock %}
{% block title %}{{ _('404 Not Found') }} - UdeS team manager{% endblock %}
{% block page_title %}{{ _('Page Not Found') }}{% endblock %}
{% block content %}
<div class="error-container">
<div class="error-icon">
<i class="fas fa-search"></i>
</div>
<h2>404 — Not Found</h2>
<p>The page you are looking for does not exist. It may have been moved or deleted.</p>
<h2>{{ _('404 — Not Found') }}</h2>
<p>{{ _('The page you are looking for does not exist. It may have been moved or deleted.') }}</p>
<a href="{{ url_for('main.dashboard') if current_user.is_authenticated else url_for('auth.login') }}" class="btn btn-primary">
<i class="fas fa-home"></i> Return Home
<i class="fas fa-home"></i> {{ _('Return Home') }}
</a>
</div>
{% endblock %}
{% endblock %}
+6 -6
View File
@@ -1,15 +1,15 @@
{% extends "layouts/base.html" %}
{% block title %}429 Too Many Requests - TryoutPro{% endblock %}
{% block page_title %}Rate Limit Exceeded{% endblock %}
{% block title %}{{ _('429 Too Many Requests') }} - UdeS team manager{% endblock %}
{% block page_title %}{{ _('Rate Limit Exceeded') }}{% endblock %}
{% block content %}
<div class="error-container">
<div class="error-icon">
<i class="fas fa-hourglass-half"></i>
</div>
<h2>429 — Too Many Requests</h2>
<p>You have sent too many requests in a short period. Please wait a moment and try again.</p>
<h2>{{ _('429 — Too Many Requests') }}</h2>
<p>{{ _('You have sent too many requests in a short period. Please wait a moment and try again.') }}</p>
<a href="{{ url_for('main.dashboard') if current_user.is_authenticated else url_for('auth.login') }}" class="btn btn-primary">
<i class="fas fa-arrow-left"></i> Go Back
<i class="fas fa-arrow-left"></i> {{ _('Go Back') }}
</a>
</div>
{% endblock %}
{% endblock %}
+12 -6
View File
@@ -1,15 +1,21 @@
{% extends "layouts/base.html" %}
{% block title %}500 Server Error - TryoutPro{% endblock %}
{% block page_title %}Internal Server Error{% endblock %}
{% block title %}{{ _('500 Server Error') }} - UdeS team manager{% endblock %}
{% block page_title %}{{ _('Internal Server Error') }}{% endblock %}
{% block content %}
<div class="error-container">
<div class="error-icon">
<i class="fas fa-cogs"></i>
</div>
<h2>500 — Internal Server Error</h2>
<p>Something went wrong on our end. The error has been logged and will be investigated. Please try again later.</p>
<h2>{{ _('500 — Internal Server Error') }}</h2>
<p>{{ _('Something went wrong on our end. The error has been logged and will be investigated. Please try again later.') }}</p>
{# The reference is what makes a report actionable: it names one request
in errors.log. It identifies nothing else — no session, no account —
so there is nothing to protect here (OBS-005). #}
{% if request_id and request_id != '-' %}
<p class="text-muted small">{{ _('Reference to quote if you report this:') }} <code>{{ request_id }}</code></p>
{% endif %}
<a href="{{ url_for('main.dashboard') if current_user.is_authenticated else url_for('auth.login') }}" class="btn btn-primary">
<i class="fas fa-redo-alt"></i> Try Again
<i class="fas fa-redo-alt"></i> {{ _('Try Again') }}
</a>
</div>
{% endblock %}
{% endblock %}
@@ -0,0 +1,19 @@
{# Language switcher.
Included from both branches of the layout: the signed-in sidebar and the
anonymous authentication page. Someone who cannot read the current
language has to be able to change it *before* signing in, so this cannot
live behind the login.
Each language is written in its own language, for the same reason. #}
<i class="fas fa-language" aria-hidden="true"></i>
<span class="sr-only">{{ _('Language') }}</span>
{% for code in supported_locales %}
{%- if code == current_locale %}
<span class="lang-current" aria-current="true">{{ locale_names[code] }}</span>
{%- else %}
<a href="{{ url_for('main.set_language', locale=code) }}"
class="lang-link" hreflang="{{ code }}" rel="alternate">{{ locale_names[code] }}</a>
{%- endif %}
{%- if not loop.last %}<span class="lang-separator" aria-hidden="true">·</span>{% endif %}
{% endfor %}
+41
View File
@@ -0,0 +1,41 @@
{#
Pagination controls (MNT-14).
Import and call:
{% import 'layouts/_pagination.html' as pager %}
{{ pager.controls(pagination) }}
`page_url` is a Jinja global registered in app.py; it rebuilds the current
URL at another page number, keeping the rest of the query string. That is
the part that gets forgotten — dropping `sort` or `team_id` from a
pagination link silently resets the view someone was looking at.
Plain <a> links only: no inline handler, nothing for CSP to refuse
(tests/test_csp.py).
#}
{% macro controls(pagination) %}
{% if pagination.pages > 1 %}
<nav class="pagination" aria-label="{{ _('Pagination') }}">
{% if pagination.has_prev %}
<a class="btn btn-secondary btn-sm" href="{{ page_url(pagination.prev_num) }}"
rel="prev">&laquo; {{ _('Previous') }}</a>
{% else %}
<span class="btn btn-secondary btn-sm is-disabled" aria-disabled="true">&laquo; {{ _('Previous') }}</span>
{% endif %}
<span class="pagination-status">
{{ _('Page %(page)s of %(pages)s', page=pagination.page, pages=pagination.pages) }}
&middot;
{{ _('%(total)s in total', total=pagination.total) }}
</span>
{% if pagination.has_next %}
<a class="btn btn-secondary btn-sm" href="{{ page_url(pagination.next_num) }}"
rel="next">{{ _('Next') }} &raquo;</a>
{% else %}
<span class="btn btn-secondary btn-sm is-disabled" aria-disabled="true">{{ _('Next') }} &raquo;</span>
{% endif %}
</nav>
{% endif %}
{% endmacro %}
+74 -29
View File
@@ -1,10 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<html lang="{{ current_locale }}">
<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">
<title>{% block title %}UdeS team manager{% endblock %}</title>
{# Subresource integrity (QUA-004). Without it, whoever controls the CDN
controls what runs on every page of this site — and the CSP names these
hosts as allowed, so it would not object.
What SRI does and does not do: it pins this exact file, so the browser
refuses a version that has been altered since. It does not prove the
file was honest when the hash was taken. This hash is the one cdnjs
publishes for the release, not one derived from the copy we downloaded.
integrity requires crossorigin. Changing the version means changing
the hash, or the asset silently stops loading. #}
<link rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"
integrity="sha512-DTOQO9RWCH3ppGqcWaEA1BIZOC6xxalwEsw9c2QQeAIftl+Vegovlnee1c9QX4TctnWMn13TZye+giMm8e2LwA=="
crossorigin="anonymous"
referrerpolicy="no-referrer">
<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>
@@ -13,8 +28,8 @@
<nav class="sidebar" id="sidebar">
<div class="sidebar-header">
<div class="logo">
<i class="fas fa-trophy"></i>
<span>TryoutPro</span>
<img src="{{ url_for('static', filename='images/UdeS_logo.png') }}" alt="UdeS Logo" class="logo-img">
<span>UdeS team manager</span>
</div>
<div class="user-badge">
<div class="user-avatar">
@@ -30,26 +45,26 @@
<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>
<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>
<span>{{ _('Tryouts') }}</span>
</a>
</li>
<li>
<a href="{{ url_for('matches.calendar') }}" class="{% if request.endpoint and 'calendar' in request.endpoint %}active{% endif %}">
<i class="fas fa-calendar"></i>
<span>Calendar</span>
<span>{{ _('Calendar') }}</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>
<span>{{ _('Evaluations') }}</span>
</a>
</li>
{% endif %}
@@ -57,14 +72,14 @@
<li>
<a href="{{ url_for('teams.my_teams') }}" class="{% if request.endpoint == 'teams.my_teams' %}active{% endif %}">
<i class="fas fa-users"></i>
<span>My Team(s)</span>
<span>{{ _('My Team(s)') }}</span>
</a>
</li>
{% else %}
<li>
<a href="{{ url_for('teams.list_teams') }}" class="{% if request.endpoint and 'teams' in request.endpoint and request.endpoint != 'teams.my_teams' %}active{% endif %}">
<i class="fas fa-users-cog"></i>
<span>Manage Teams</span>
<span>{{ _('Manage Teams') }}</span>
</a>
</li>
{% endif %}
@@ -72,7 +87,7 @@
<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>
<span>{{ _('Manage Users') }}</span>
</a>
</li>
<li>
@@ -86,7 +101,7 @@
<li>
<a href="{{ url_for('users.my_notes') }}" class="{% if request.endpoint == 'users.my_notes' %}active{% endif %}">
<i class="fas fa-sticky-note"></i>
<span>My Notes</span>
<span>{{ _('My Notes') }}</span>
</a>
</li>
{% endif %}
@@ -94,42 +109,53 @@
<li>
<a href="{{ url_for('users.notes_dashboard') }}" class="{% if request.endpoint == 'users.notes_dashboard' or request.endpoint == 'users.manage_team_notes' or request.endpoint == 'users.manage_personal_notes' %}active{% endif %}">
<i class="fas fa-sticky-note"></i>
<span>Notes & One on One</span>
<span>{{ _('Notes & One on One') }}</span>
</a>
</li>
{% endif %}
<li>
<a href="{{ url_for('users.list_contracts') }}" class="{% if request.endpoint and 'contracts' in request.endpoint %}active{% endif %}">
<i class="fas fa-file-contract"></i>
<span>Contracts</span>
<span>{{ _('Contracts') }}</span>
</a>
</li>
<li class="nav-divider"></li>
<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>
<span>{{ _('My Profile') }}</span>
</a>
</li>
<li>
<a href="{{ url_for('auth.logout') }}" class="logout-link">
<i class="fas fa-sign-out-alt"></i>
<span>Logout</span>
</a>
{# A form, not a link: logging out is a state change, and a
GET route carries no CSRF token — any site could sign the
user out with an <img> tag. Styled as a nav entry. #}
<form method="POST" action="{{ url_for('auth.logout') }}" class="nav-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<button type="submit" class="logout-link">
<i class="fas fa-sign-out-alt"></i>
<span>{{ _('Logout') }}</span>
</button>
</form>
</li>
<li class="nav-divider"></li>
<li class="nav-language">
{% include "layouts/_language_switcher.html" %}
</li>
</ul>
</nav>
<div class="main-content" id="mainContent">
<header class="top-bar">
<button class="sidebar-toggle" id="sidebarToggle" onclick="toggleSidebar()">
<button class="sidebar-toggle" id="sidebarToggle" data-action="toggle-sidebar">
<i class="fas fa-bars"></i>
</button>
<div class="page-header">
<h1>{% block page_title %}Dashboard{% endblock %}</h1>
<h1>{% block page_title %}{{ _('Dashboard') }}{% endblock %}</h1>
{% block breadcrumb %}{% endblock %}
</div>
<button class="dark-mode-toggle" id="darkModeToggle" onclick="toggleDarkMode()" title="Toggle dark mode">
<button class="dark-mode-toggle" id="darkModeToggle" data-action="toggle-dark-mode"
title="{{ _('Toggle dark mode') }}">
<i class="fas fa-moon"></i>
</button>
{% block header_actions %}{% endblock %}
@@ -140,7 +166,8 @@
{% 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>
<button type="button" class="alert-close" data-action="dismiss-alert"
aria-label="{{ _('Dismiss') }}">&times;</button>
</div>
{% endfor %}
{% endif %}
@@ -158,7 +185,8 @@
{% 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>
<button type="button" class="alert-close" data-action="dismiss-alert"
aria-label="{{ _('Dismiss') }}">&times;</button>
</div>
{% endfor %}
{% endif %}
@@ -166,10 +194,27 @@
</div>
<div class="auth-container">
<div class="auth-header">
<i class="fas fa-trophy"></i>
<h2>TryoutPro</h2>
<p>Team Tryout Management System</p>
<img src="{{ url_for('static', filename='images/UdeS_logo.png') }}" alt="UdeS Logo" class="auth-logo-img">
<h2>UdeS team manager</h2>
<p>{{ _('UdeS team manager') }}</p>
</div>
<div class="auth-language">
{% include "layouts/_language_switcher.html" %}
</div>
{# The same `content` block as the signed-in branch, rendered
here too — `self.content()` rather than a second
`{% block %}`, which Jinja refuses.
The error pages (400, 403, 404, 429, 500) all fill `content`,
and it existed only inside the `is_authenticated` branch: a
signed-out visitor hitting any of them got the logo, the
language switcher and no message whatsoever. The <title> still
said "404", which is most of why nobody noticed.
Only one branch of the `if` runs, so this never double-renders.
Sign-in pages fill `auth_content` instead and leave this
empty. #}
{{ self.content() }}
{% block auth_content %}{% endblock %}
</div>
</div>
@@ -178,4 +223,4 @@
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
{% block scripts %}{% endblock %}
</body>
</html>
</html>
+4 -3
View File
@@ -7,7 +7,7 @@
{# Page Header Macro - renders title and breadcrumb #}
{% macro page_header(title, breadcrumb) %}
{% block title %}{{ title }} - TryoutPro{% endblock %}
{% block title %}{{ title }} - UdeS team manager{% endblock %}
{% block page_title %}{{ title }}{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">{{ breadcrumb }}</span>{% endblock %}
{% endmacro %}
@@ -108,11 +108,12 @@
{# Modal Macro - renders a modal dialog #}
{% macro modal(id, title, content, footer_buttons=None) %}
<div id="{{ id }}" class="modal hidden">
<div class="modal-backdrop" onclick="hideModal('{{ id }}')"></div>
<div class="modal-backdrop" data-action="hide-modal" data-modal-id="{{ id }}"></div>
<div class="modal-content">
<div class="modal-header">
<h3>{{ title }}</h3>
<button class="modal-close" onclick="hideModal('{{ id }}')">&times;</button>
<button class="modal-close" data-action="hide-modal" data-modal-id="{{ id }}"
aria-label="{{ _('Close') }}">&times;</button>
</div>
<div class="modal-body">
{{ content }}
+16 -16
View File
@@ -1,6 +1,6 @@
{% extends "layouts/base.html" %}
{% block title %}Add Personal Note - TryoutPro{% endblock %}
{% block page_title %}Add Personal Note{% endblock %}
{% block title %}{{ _('Add Personal Note') }} - UdeS team manager{% endblock %}
{% block page_title %}{{ _('Add Personal Note') }}{% endblock %}
{% block breadcrumb %}
<span class="breadcrumb">
Home / <a href="{{ url_for('tryouts.list_tryouts') }}">Tryouts</a>
@@ -19,7 +19,7 @@
{% block content %}
<div class="card">
<div class="card-header">
<h3><i class="fas fa-sticky-note"></i> Add Personal Note</h3>
<h3><i class="fas fa-sticky-note"></i> {{ _('Add Personal Note') }}</h3>
{% if context_type == 'tryout' %}
<p class="text-muted small">Context: <strong>Tryout - {{ tryout.title }}</strong></p>
{% elif context_type == 'match' %}
@@ -44,9 +44,9 @@
{% endif %}
<div class="form-group">
<label for="player_id">Player</label>
<label for="player_id">{{ _('Player') }}</label>
<select name="player_id" id="player_id" class="form-select" required>
<option value="">-- Select a player --</option>
<option value="">{{ _('-- Select a player --') }}</option>
{% for player in players %}
<option value="{{ player.id }}" {% if preselected_player_id == player.id %}selected{% endif %}>
{{ player.username }}
@@ -56,15 +56,15 @@
</div>
<div class="form-group">
<label for="content">Note Content</label>
<textarea name="content" id="content" rows="6" class="form-textarea" placeholder="Enter your coaching notes..." required></textarea>
<label for="content">{{ _('Note Content') }}</label>
<textarea name="content" id="content" rows="6" class="form-textarea" placeholder="{{ _('Enter your coaching notes...') }}" required></textarea>
</div>
{% if context_type != 'tryout' and context_type != 'match' %}
<div class="form-group">
<label for="tryout_id">Link to Tryout (Optional)</label>
<label for="tryout_id">{{ _('Link to Tryout (Optional)') }}</label>
<select name="tryout_id" id="tryout_id" class="form-select">
<option value="">-- No tryout --</option>
<option value="">{{ _('-- No tryout --') }}</option>
{% for t in tryouts %}
<option value="{{ t.id }}">{{ t.title }} ({{ t.date.strftime('%Y-%m-%d') }})</option>
{% endfor %}
@@ -72,9 +72,9 @@
</div>
<div class="form-group">
<label for="match_id">Link to Match (Optional)</label>
<label for="match_id">{{ _('Link to Match (Optional)') }}</label>
<select name="match_id" id="match_id" class="form-select">
<option value="">-- No match --</option>
<option value="">{{ _('-- No match --') }}</option>
{% for m in matches %}
<option value="{{ m.id }}">{{ m.title }} ({{ m.date.strftime('%Y-%m-%d') }})</option>
{% endfor %}
@@ -82,9 +82,9 @@
</div>
<div class="form-group">
<label for="team_id">Link to Team (Optional)</label>
<label for="team_id">{{ _('Link to Team (Optional)') }}</label>
<select name="team_id" id="team_id" class="form-select">
<option value="">-- No team --</option>
<option value="">{{ _('-- No team --') }}</option>
{% for team in teams %}
<option value="{{ team.id }}">{{ team.name }}</option>
{% endfor %}
@@ -94,7 +94,7 @@
{% if team_notes %}
<div class="form-group">
<label>Team Notes (Reference)</label>
<label>{{ _('Team Notes (Reference)') }}</label>
<div class="team-notes-reference">
{% for note in team_notes %}
<div class="note-reference-item">
@@ -117,7 +117,7 @@
{% endif %}
" class="btn btn-secondary">Cancel</a>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> Add Note
<i class="fas fa-save"></i> {{ _('Add Note') }}
</button>
</div>
</form>
@@ -149,4 +149,4 @@
font-style: italic;
}
</style>
{% endblock %}
{% endblock %}
+277
View File
@@ -0,0 +1,277 @@
{% extends "layouts/base.html" %}
{% block title %}Admin Panel{% endblock %}
{% block page_title %}Admin Panel{% endblock %}
{% block content %}
<!-- Stats Bar -->
<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"></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-shield-alt"></i>
</div>
<div class="stat-info">
<h3>{{ stats.total_org_teams }}</h3>
<p>Org Teams</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon bg-info">
<i class="fas fa-calendar-alt"></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">
<!-- Left column -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-toggle-{% if settings.tryouts_open %}on{% else %}off{% endif %}"></i> Tryouts Access</h3>
</div>
<div class="card-body">
<p>
Status:
<span class="badge badge-{% if settings.tryouts_open %}success{% else %}danger{% endif %}">
{% if settings.tryouts_open %}OPEN{% else %}CLOSED{% endif %}
</span>
</p>
<p class="text-muted" style="font-size: 0.85rem; margin: 8px 0;">
{% if settings.tryouts_open %}
Coaches and managers can create and modify tryouts.
{% else %}
Only admins can create or modify tryouts. Coaches and managers are locked out.
{% endif %}
</p>
<form method="POST" action="{{ url_for('admin.toggle_tryouts') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<button type="submit" class="btn btn-{% if settings.tryouts_open %}warning{% else %}success{% endif %}">
{% if settings.tryouts_open %}Close Tryouts{% else %}Open Tryouts{% endif %}
</button>
</form>
</div>
</div>
<div class="card">
<div class="card-header">
<h3><i class="fas fa-calendar-check"></i> Season Management</h3>
</div>
<div class="card-body">
<p>
Season:
<span class="badge badge-{% if settings.season_active %}success{% else %}info{% endif %}">
{% if settings.season_active %}ACTIVE{% else %}INACTIVE{% endif %}
</span>
</p>
<p><strong>Name:</strong> {{ settings.season_name }}</p>
<p><strong>Start:</strong> {{ settings.season_start }}</p>
<p><strong>End:</strong> {{ settings.season_end }}</p>
{% if not settings.season_active %}
<hr style="margin: 12px 0;">
<form method="POST" action="{{ url_for('admin.start_season') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="form-group">
<label>Season Name</label>
<input type="text" name="season_name" class="form-input" placeholder="e.g. Fall 2026" required>
</div>
<div class="form-group">
<label>Start Date</label>
<input type="date" name="season_start" class="form-input" required>
</div>
<button type="submit" class="btn btn-success">Begin Season</button>
</form>
{% else %}
<hr style="margin: 12px 0;">
<form method="POST" action="{{ url_for('admin.end_season') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="form-group">
<label>End Date</label>
<input type="date" name="season_end" class="form-input" required>
</div>
<button type="submit" class="btn btn-warning">End Season</button>
</form>
{% endif %}
</div>
</div>
<!-- Wipe Teams -->
<div class="card" style="border-left: 4px solid var(--danger);">
<div class="card-header">
<h3><i class="fas fa-trash-alt" style="color: var(--danger);"></i> Wipe Teams for New Season</h3>
</div>
<div class="card-body">
<p class="text-muted" style="font-size: 0.85rem; margin-bottom: 12px;">
This removes all players from organization teams and deletes all regular-season matches.
Team structures, coaches, and managers are preserved. A safety backup is created automatically.
</p>
<form method="POST" action="{{ url_for('admin.wipe_teams') }}" onsubmit="return confirmWipe()">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="form-group">
<label>Type <strong>WIPE</strong> to confirm:</label>
<input type="text" name="confirm" class="form-input" placeholder="WIPE" autocomplete="off" required>
</div>
<button type="submit" class="btn btn-danger">Wipe Team Rosters</button>
</form>
</div>
</div>
<!-- Manual Backup -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-database"></i> Manual Backup</h3>
</div>
<div class="card-body">
<p class="text-muted" style="font-size: 0.85rem; margin-bottom: 12px;">
Creates a full PostgreSQL database dump (.sql file). Stored on the server.
</p>
<form method="POST" action="{{ url_for('admin.create_backup') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="form-group">
<label>Notes (optional)</label>
<input type="text" name="notes" class="form-input" placeholder="What's this backup for?">
</div>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> Create Backup Now
</button>
</form>
</div>
</div>
<!-- Backup History -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-history"></i> Backup History & Restore</h3>
</div>
<div class="card-body">
{% if backups %}
<div class="table-container">
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>Filename</th>
<th>Size</th>
<th>Type</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for b in backups %}
<tr>
<td>{{ b.created_at.strftime('%Y-%m-%d %H:%M') if b.created_at else '—' }}</td>
<td style="max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;" title="{{ b.filename }}">{{ b.filename }}</td>
<td>{{ (b.size_bytes / 1024)|round(1) }} KB</td>
<td><span class="badge badge-info">{{ b.backup_type }}</span></td>
<td style="white-space: nowrap;">
<a href="{{ url_for('admin.download_backup', backup_id=b.id) }}" class="btn btn-sm btn-primary" title="Download">
<i class="fas fa-download"></i>
</a>
<form method="POST" action="{{ url_for('admin.restore_backup', backup_id=b.id) }}" class="inline-form"
onsubmit="return confirm('Restore backup {{ b.filename }}? This will overwrite all current data. A safety backup will be made first.');">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<button type="submit" class="btn btn-sm btn-warning" title="Restore">
<i class="fas fa-undo"></i>
</button>
</form>
<form method="POST" action="{{ url_for('admin.delete_backup', backup_id=b.id) }}" class="inline-form"
onsubmit="return confirm('Delete backup {{ b.filename }}?');">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<button type="submit" class="btn btn-sm btn-danger" title="Delete">
<i class="fas fa-trash"></i>
</button>
</form>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-muted" style="font-size: 0.85rem;">No backups yet. Create your first backup above.</p>
{% endif %}
</div>
</div>
</div>
<!-- Audit Log -->
<div class="card mt-4">
<div class="card-header">
<h3><i class="fas fa-clipboard-list"></i> Audit Log (Recent)</h3>
</div>
<div class="card-body">
{% if audit_logs %}
<div class="table-container">
<table class="table">
<thead>
<tr>
<th>Date</th>
<th>User</th>
<th>Action</th>
<th>Details</th>
<th>IP</th>
</tr>
</thead>
<tbody>
{% for entry in audit_logs %}
<tr>
<td>{{ entry.created_at.strftime('%Y-%m-%d %H:%M') if entry.created_at else '—' }}</td>
<td>{{ entry.user.username if entry.user else 'System' }}</td>
<td><span class="badge badge-info">{{ entry.action }}</span></td>
<td style="max-width: 250px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;">{{ entry.details or '—' }}</td>
<td>{{ entry.ip_address or '—' }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<p class="text-muted" style="font-size: 0.85rem;">No audit log entries yet.</p>
{% endif %}
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
function confirmWipe() {
var input = document.querySelector('input[name="confirm"]');
if (input.value.trim() !== 'WIPE') {
alert('You must type WIPE to confirm.');
return false;
}
return confirm('This will remove ALL players from organization teams and delete ALL regular-season matches. A safety backup will be created automatically. Are you ABSOLUTELY sure?');
}
</script>
{% endblock %}
+85
View File
@@ -0,0 +1,85 @@
{% extends "layouts/base.html" %}
{% block title %}Evaluate Players - UdeS team manager{% endblock %}
{% block page_title %}Evaluate Players{% 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 %}
<form method="POST" action="{{ url_for('evaluations.batch_evaluate', tryout_id=tryout.id) }}" class="form batch-eval-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
{% for entry in players %}
<input type="hidden" name="player_ids" value="{{ entry.player.id }}"/>
{% endfor %}
{% set positions = game_positions.get(tryout.game, []) %}
<div class="batch-eval-grid">
{% for entry in players %}
<div class="card">
<div class="card-header">
<h3>
<i class="fas fa-user"></i> {{ entry.player.username }}
{% if entry.existing %}
<span class="badge badge-success">Already Evaluated</span>
{% else %}
<span class="badge badge-warning">Not Evaluated</span>
{% endif %}
</h3>
</div>
<div class="card-body">
<div class="eval-player-info mb-4">
<div class="user-avatar avatar-lg">{{ entry.player.username[:2] | upper }}</div>
<div>
<h3>{{ entry.player.username }}</h3>
<p class="text-muted">{{ entry.player.email }} | {{ entry.player.phone or 'No phone' }}</p>
</div>
</div>
{% set pid = entry.player.id %}
{% set existing = entry.existing %}
{% set existing_scores = entry.existing_scores %}
<div class="form-row">
{% for field_name, label in evaluation_criteria %}
<div class="form-group col-4">
<label for="{{ field_name }}_{{ pid }}">{{ label }} (1-10)</label>
<div class="score-input">
<input type="range" id="{{ field_name }}_{{ pid }}" name="{{ field_name }}_{{ pid }}" min="1" max="10" value="{{ existing_scores[field_name] or 5 }}" data-mirror>
<span class="range-value">{{ existing_scores[field_name] or 5 }}</span>
</div>
</div>
{% endfor %}
</div>
<div class="form-row">
<div class="form-group col-12">
<label for="position_recommendation_{{ pid }}">Recommended Position</label>
{% if positions %}
<select id="position_recommendation_{{ pid }}" name="position_recommendation_{{ pid }}" class="form-select">
<option value="">-- Select Position --</option>
{% for pos in positions %}
<option value="{{ pos }}" {% if existing and existing.position_recommendation == pos %}selected{% endif %}>{{ pos }}</option>
{% endfor %}
</select>
{% else %}
<input type="text" id="position_recommendation_{{ pid }}" name="position_recommendation_{{ pid }}" value="{{ existing.position_recommendation if existing else '' }}" placeholder="Enter position (optional)">
{% endif %}
</div>
</div>
<div class="form-group">
<label for="comments_{{ pid }}">Comments</label>
<textarea id="comments_{{ pid }}" name="comments_{{ pid }}" rows="3" placeholder="Enter your evaluation notes...">{{ existing.comments if existing else '' }}</textarea>
</div>
</div>
</div>
{% endfor %}
</div>
<div class="form-actions">
<a href="{{ url_for('evaluations.players_to_evaluate', tryout_id=tryout.id) }}" class="btn btn-secondary">Back</a>
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> Save All Evaluations
</button>
</div>
</form>
{% endblock %}
+173 -137
View File
@@ -1,25 +1,25 @@
{% extends "layouts/base.html" %}
{% block title %}Calendar - TryoutPro{% endblock %}
{% block page_title %}Calendar{% endblock %}
{% block title %}{{ _('Calendar') }} - UdeS team manager{% endblock %}
{% block page_title %}{{ _('Calendar') }}{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / Calendar</span>{% endblock %}
{% block content %}
<div class="card">
<div class="card-header">
<h3><i class="fas fa-calendar-alt"></i> Schedule</h3>
<h3><i class="fas fa-calendar-alt"></i> {{ _('Schedule') }}</h3>
<div class="header-actions">
<div class="btn-group" role="group">
<button type="button" class="btn btn-sm btn-outline" onclick="changeView('dayGridMonth')">
<i class="fas fa-calendar"></i> Month
<button type="button" class="btn btn-sm btn-outline" data-action="change-view" data-view="dayGridMonth">
<i class="fas fa-calendar"></i> {{ _('Month') }}
</button>
<button type="button" class="btn btn-sm btn-outline" onclick="changeView('timeGridWeek')">
<i class="fas fa-calendar-week"></i> Week
<button type="button" class="btn btn-sm btn-outline" data-action="change-view" data-view="timeGridWeek">
<i class="fas fa-calendar-week"></i> {{ _('Week') }}
</button>
<button type="button" class="btn btn-sm btn-outline" onclick="changeView('timeGridDay')">
<i class="fas fa-calendar-day"></i> Day
<button type="button" class="btn btn-sm btn-outline" data-action="change-view" data-view="timeGridDay">
<i class="fas fa-calendar-day"></i> {{ _('Day') }}
</button>
<button type="button" class="btn btn-sm btn-outline" onclick="changeView('listMonth')">
<i class="fas fa-list"></i> List
<button type="button" class="btn btn-sm btn-outline" data-action="change-view" data-view="listMonth">
<i class="fas fa-list"></i> {{ _('List') }}
</button>
</div>
</div>
@@ -31,26 +31,26 @@
<!-- Create Event Modal (for clicking empty days) -->
<div id="createEventModal" class="modal hidden">
<div class="modal-backdrop" onclick="hideCreateEventModal()"></div>
<div class="modal-backdrop" data-action="hide-create-event"></div>
<div class="modal-content">
<div class="modal-header">
<h3><i class="fas fa-plus-circle"></i> Create New Event</h3>
<button class="modal-close" onclick="hideCreateEventModal()">&times;</button>
<h3><i class="fas fa-plus-circle"></i> {{ _('Create New Event') }}</h3>
<button class="modal-close" data-action="hide-create-event">&times;</button>
</div>
<div class="modal-body">
<p class="mb-3"><strong>Date:</strong> <span id="createEventDate"></span></p>
<p class="mb-3"><strong>{{ _('Date:') }}</strong> <span id="createEventDate"></span></p>
<input type="hidden" id="createEventDateInput"/>
<div class="card mb-3">
<div class="card-body">
<h5><i class="fas fa-futbol"></i> Schedule Tryout Match</h5>
<p class="text-muted small">Add a scrim/match inside an existing tryout</p>
<h5><i class="fas fa-futbol"></i> {{ _('Schedule Tryout Match') }}</h5>
<p class="text-muted small">{{ _('Add a scrim/match inside an existing tryout') }}</p>
<div class="form-inline">
<select id="createTryoutSelect" class="form-select" style="flex:1;">
<option value="">-- Select a tryout --</option>
<option value="">{{ _('-- Select a tryout --') }}</option>
</select>
<button class="btn btn-sm btn-primary ml-2" onclick="goToTryoutMatch()">
<i class="fas fa-arrow-right"></i> Go
<button class="btn btn-sm btn-primary ml-2" data-action="go-tryout-match">
<i class="fas fa-arrow-right"></i> {{ _('Go') }}
</button>
</div>
</div>
@@ -58,14 +58,14 @@
<div class="card mb-3">
<div class="card-body">
<h5><i class="fas fa-users"></i> Schedule Team Match</h5>
<p class="text-muted small">Regular season match for an org team</p>
<h5><i class="fas fa-users"></i> {{ _('Schedule Team Match') }}</h5>
<p class="text-muted small">{{ _('Regular season match for an org team') }}</p>
<div class="form-inline">
<select id="createTeamSelect" class="form-select" style="flex:1;">
<option value="">-- Select a team --</option>
<option value="">{{ _('-- Select a team --') }}</option>
</select>
<button class="btn btn-sm btn-success ml-2" onclick="goToTeamMatch()">
<i class="fas fa-arrow-right"></i> Go
<button class="btn btn-sm btn-success ml-2" data-action="go-team-match">
<i class="fas fa-arrow-right"></i> {{ _('Go') }}
</button>
</div>
</div>
@@ -73,10 +73,10 @@
<div class="card">
<div class="card-body">
<h5><i class="fas fa-calendar-plus"></i> Create New Tryout</h5>
<p class="text-muted small">Create a brand new tryout event</p>
<button class="btn btn-sm btn-info" onclick="goToCreateTryout()">
<i class="fas fa-plus"></i> Create Tryout
<h5><i class="fas fa-calendar-plus"></i> {{ _('Create New Tryout') }}</h5>
<p class="text-muted small">{{ _('Create a brand new tryout event') }}</p>
<button class="btn btn-sm btn-info" data-action="go-create-tryout">
<i class="fas fa-plus"></i> {{ _('Create Tryout') }}
</button>
</div>
</div>
@@ -86,26 +86,26 @@
<!-- Event Details Modal -->
<div id="eventModal" class="modal hidden">
<div class="modal-backdrop" onclick="hideEventModal()"></div>
<div class="modal-backdrop" data-action="hide-event-modal"></div>
<div class="modal-content">
<div class="modal-header">
<h3 id="modalTitle">Event Details</h3>
<button class="modal-close" onclick="hideEventModal()">&times;</button>
<h3 id="modalTitle">{{ _('Event Details') }}</h3>
<button class="modal-close" data-action="hide-event-modal">&times;</button>
</div>
<div class="modal-body">
<div id="modalContent"></div>
<div id="modalPresenceToggle" class="form-actions mt-3" style="display: none; justify-content: center;">
<button class="btn btn-sm btn-outline" id="calPresenceBtn">Confirm</button>
<button class="btn btn-sm btn-outline" id="calPresenceBtn">{{ _('Confirm') }}</button>
</div>
<div id="modalActions" class="form-actions mt-3" style="display: none;">
<button class="btn btn-sm btn-danger" id="deleteMatchBtn" style="display: none;">
<i class="fas fa-trash"></i> Delete Match
<i class="fas fa-trash"></i> {{ _('Delete Match') }}
</button>
<button class="btn btn-sm btn-primary" id="editMatchBtn" style="display: none;">
<i class="fas fa-edit"></i> Edit Match
<i class="fas fa-edit"></i> {{ _('Edit Match') }}
</button>
<button class="btn btn-sm btn-primary" id="viewTryoutBtn" style="display: none;">
<i class="fas fa-eye"></i> View Tryout
<i class="fas fa-eye"></i> {{ _('View Tryout') }}
</button>
</div>
</div>
@@ -114,9 +114,20 @@
{% endblock %}
{% block scripts %}
<link href="https://cdn.jsdelivr.net/npm/[email protected]/index.global.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/fullcalendar@6.1.10/index.global.min.js"></script>
<script>
{# The stylesheet that used to sit here — index.global.min.css — does not
exist. FullCalendar 6 bundles its styles into the JS, and that file is not
in the published package: the link had been answering 404 on every calendar
load since the upgrade. A failed stylesheet is silent in the browser, which
is why it survived.
Integrity pins the bundle (QUA-004): this file is executable script from a
third party, on the page that shows every match in the club. See the note
in layouts/base.html for what SRI does and does not promise. #}
<script src="https://cdn.jsdelivr.net/npm/[email protected]/index.global.min.js"
integrity="sha384-WfE/vOHqht3KDj6FvpwQUf3UxEPUHoGJ3w1yZ8rhpLWnVigt8HjXL2zXqtcfS7mf"
crossorigin="anonymous"
referrerpolicy="no-referrer"></script>
<script nonce="{{ csp_nonce }}">
var canScheduleMatches = {% if current_user.can_schedule_matches() %}true{% else %}false{% endif %};
document.addEventListener('DOMContentLoaded', function() {
@@ -205,14 +216,16 @@ function fetchTryoutOptions() {
fetch('/matches/api/manageable-tryouts')
.then(function(r) { return r.json(); })
.then(function(data) {
// new Option() sets the label as text; concatenating it into
// innerHTML let a tryout title carry markup into the page.
var sel = document.getElementById('createTryoutSelect');
sel.innerHTML = '<option value="">-- Select a tryout --</option>';
sel.replaceChildren(new Option('-- Select a tryout --', ''));
var today = new Date().toISOString().split('T')[0];
data.forEach(function(t) {
// Only show tryouts that haven't ended
var tryoutEndDate = t.end_date || t.date;
if (tryoutEndDate >= today) {
sel.innerHTML += '<option value="' + t.id + '">' + t.title + ' (' + t.date + ')</option>';
sel.appendChild(new Option(t.title + ' (' + t.date + ')', t.id));
}
});
})
@@ -224,109 +237,130 @@ function fetchTeamOptions() {
.then(function(r) { return r.json(); })
.then(function(data) {
var sel = document.getElementById('createTeamSelect');
sel.innerHTML = '<option value="">-- Select a team --</option>';
sel.replaceChildren(new Option('-- Select a team --', ''));
data.forEach(function(t) {
sel.innerHTML += '<option value="' + t.id + '">' + t.name + '</option>';
sel.appendChild(new Option(t.name, t.id));
});
})
.catch(function() {});
}
// ---------------------------------------------------------------------------
// Safe DOM builders
// ---------------------------------------------------------------------------
// Everything rendered in the event modal originates from a JSON endpoint,
// where no HTML escaping applies. Text therefore goes through textContent,
// never through innerHTML.
function makeEl(tag, className, text) {
var node = document.createElement(tag);
if (className) { node.className = className; }
if (text !== undefined && text !== null) { node.textContent = text; }
return node;
}
function detailItem(label, valueNode, fullWidth) {
var item = makeEl('div', 'detail-item' + (fullWidth ? ' full-width' : ''));
item.appendChild(makeEl('span', 'detail-label', label));
var value = makeEl('span', 'detail-value');
value.appendChild(valueNode);
item.appendChild(value);
return item;
}
// Renders newlines as <br> without letting any other markup through.
function multilineNode(text) {
var fragment = document.createDocumentFragment();
String(text).split('\n').forEach(function(line, index) {
if (index > 0) { fragment.appendChild(document.createElement('br')); }
fragment.appendChild(document.createTextNode(line));
});
return fragment;
}
// A team block: its name, plus an optional list of player names.
function teamNode(name, players) {
var team = makeEl('div', 'match-team');
team.appendChild(makeEl('span', 'team-name', name));
if (players) {
var list = makeEl('ul', 'team-players-list');
players.forEach(function(player) {
list.appendChild(makeEl('li', null, player));
});
team.appendChild(list);
}
return team;
}
function versusNode(left, right) {
var wrap = makeEl('div', 'match-teams');
wrap.appendChild(left);
wrap.appendChild(makeEl('div', 'match-vs', 'vs'));
wrap.appendChild(right);
return wrap;
}
function buildTeamsNode(props) {
var sides = props.participants.split(' vs ');
if (props.match_type === 'team_vs_team' && sides.length >= 2) {
return versusNode(teamNode(sides[0]), teamNode(sides[1]));
}
if (props.match_type === 'player_vs_player' && sides.length >= 2) {
return versusNode(
teamNode('Team 1', sides[0].split(', ')),
teamNode('Team 2', sides[1].split(', '))
);
}
return document.createTextNode(props.participants);
}
function showEventModal(event) {
var props = event.extendedProps;
var title = event.title;
var type = props.type;
var date = event.start ? event.start.toDateString() : '';
var content = '<div class="detail-grid">';
if (type === 'one_on_one') {
// One on One session modal
var startTime = props.start_time ? formatTime(props.start_time) : null;
var endTime = props.end_time ? formatTime(props.end_time) : null;
var timeRange = startTime ? startTime + (endTime ? ' - ' + endTime : '') : 'TBD';
content += '<div class="detail-item"><span class="detail-label">Type</span><span class="detail-value">';
content += '<span class="badge badge-info">One on One Session</span>';
content += '</span></div>';
content += '<div class="detail-item"><span class="detail-label">Title</span><span class="detail-value">' + title + '</span></div>';
content += '<div class="detail-item"><span class="detail-label">Date</span><span class="detail-value">' + date + '</span></div>';
content += '<div class="detail-item"><span class="detail-label">Time</span><span class="detail-value">' + timeRange + '</span></div>';
content += '<div class="detail-item"><span class="detail-label">Location</span><span class="detail-value">' + (props.location || 'TBD') + '</span></div>';
content += '<div class="detail-item"><span class="detail-label">Participants</span><span class="detail-value">' + (props.participants || '') + '</span></div>';
content += '<div class="detail-item"><span class="detail-label">Status</span><span class="detail-value">';
content += '<span class="badge badge-success">Approved</span>';
content += '</span></div>';
if (props.description) {
content += '<div class="detail-item full-width"><span class="detail-label">Discussion Points</span><span class="detail-value">' + props.description + '</span></div>';
}
} else {
// Match modal (existing behavior)
content += '<div class="detail-item"><span class="detail-label">Type</span><span class="detail-value">';
content += '<span class="badge badge-' + (props.match_type === 'team_vs_team' || props.match_type === 'player_vs_player' ? 'success' : 'warning') + '">';
content += (props.match_type === 'team_vs_team' ? 'Team Match' : (props.match_type === 'player_vs_player' ? 'Player Match' : 'Player Scrim')) + '</span>';
content += '</span></div>';
content += '<div class="detail-item"><span class="detail-label">Title</span><span class="detail-value">' + title + '</span></div>';
content += '<div class="detail-item"><span class="detail-label">Date</span><span class="detail-value">' + date + '</span></div>';
content += '<div class="detail-item"><span class="detail-label">Location</span><span class="detail-value">' + (props.location || 'TBD') + '</span></div>';
content += '<div class="detail-item"><span class="detail-label">Status</span><span class="detail-value">';
content += '<span class="badge badge-' + (props.status || 'scheduled') + '">' + (props.status || 'scheduled') + '</span>';
content += '</span></div>';
// Add team separation for matches
if (props.participants) {
content += '<div class="detail-item full-width"><span class="detail-label">Teams</span><span class="detail-value">';
if (props.match_type === 'team_vs_team') {
var teams = props.participants.split(' vs ');
if (teams.length >= 2) {
content += '<div class="match-teams">';
content += '<div class="match-team"><span class="team-name">' + teams[0] + '</span></div>';
content += '<div class="match-vs">vs</div>';
content += '<div class="match-team"><span class="team-name">' + teams[1] + '</span></div>';
content += '</div>';
} else {
content += props.participants;
}
} else if (props.match_type === 'player_vs_player') {
var parts = props.participants.split(' vs ');
if (parts.length >= 2) {
content += '<div class="match-teams">';
content += '<div class="match-team"><span class="team-name">Team 1</span><ul class="team-players-list"><li>' + parts[0].split(', ').join('</li><li>') + '</li></ul></div>';
content += '<div class="match-vs">vs</div>';
content += '<div class="match-team"><span class="team-name">Team 2</span><ul class="team-players-list"><li>' + parts[1].split(', ').join('</li><li>') + '</li></ul></div>';
content += '</div>';
} else {
content += props.participants;
}
} else {
content += props.participants;
}
content += '</span></div>';
}
if (props.description) {
content += '<div class="detail-item full-width"><span class="detail-label">Description</span><span class="detail-value">' + props.description + '</span></div>';
}
// Built as DOM nodes rather than concatenated HTML. Every value below —
// match title, location, description, and above all the participant list,
// which is made of user-chosen usernames — comes from a JSON API and has
// never been HTML-escaped. Assigning it to innerHTML executed it.
var grid = makeEl('div', 'detail-grid');
var typeLabel = props.match_type === 'team_vs_team' ? 'Team Match'
: (props.match_type === 'player_vs_player' ? 'Player Match' : 'Player Scrim');
var typeBadgeClass = (props.match_type === 'team_vs_team' || props.match_type === 'player_vs_player')
? 'success' : 'warning';
grid.appendChild(detailItem('Type', makeEl('span', 'badge badge-' + typeBadgeClass, typeLabel)));
grid.appendChild(detailItem('Title', document.createTextNode(title)));
grid.appendChild(detailItem('Date', document.createTextNode(date)));
grid.appendChild(detailItem('Location', document.createTextNode(props.location || 'TBD')));
var status = props.status || 'scheduled';
grid.appendChild(detailItem('Status', makeEl('span', 'badge badge-' + status, status)));
if (type === 'match' && props.participants) {
grid.appendChild(detailItem('Teams', buildTeamsNode(props), true));
}
content += '</div>';
document.getElementById('modalTitle').textContent = (type === 'one_on_one' ? 'One on One Session' : 'Match Details');
document.getElementById('modalContent').innerHTML = content;
if (props.description) {
grid.appendChild(detailItem('Description', multilineNode(props.description), true));
}
var modalContent = document.getElementById('modalTitle');
modalContent.textContent = 'Match Details';
var target = document.getElementById('modalContent');
target.textContent = '';
target.appendChild(grid);
// Reset buttons
document.getElementById('deleteMatchBtn').style.display = 'none';
document.getElementById('editMatchBtn').style.display = 'none';
document.getElementById('viewTryoutBtn').style.display = 'none';
// Hide match actions and presence toggle for one-on-one events
if (type === 'one_on_one') {
document.getElementById('modalActions').style.display = 'none';
document.getElementById('modalPresenceToggle').style.display = 'none';
document.getElementById('eventModal').classList.remove('hidden');
return;
}
// Show action buttons for matches (coaches and above)
if (type === 'match' && canScheduleMatches) {
document.getElementById('modalActions').style.display = 'flex';
@@ -362,17 +396,6 @@ function showEventModal(event) {
document.getElementById('eventModal').classList.remove('hidden');
}
function formatTime(timeStr) {
if (!timeStr) return null;
var parts = timeStr.split(':');
var h = parseInt(parts[0]);
var m = parts[1];
var ampm = h >= 12 ? 'PM' : 'AM';
var displayHour = h % 12;
if (displayHour === 0) displayHour = 12;
return displayHour + ':' + m + ' ' + ampm;
}
function deleteCalendarMatch(matchId) {
fetch('/matches/' + matchId + '/delete', {
method: 'POST',
@@ -422,5 +445,18 @@ function toggleCalendarPresence(matchId, participantId, btn) {
function hideEventModal() {
document.getElementById('eventModal').classList.add('hidden');
}
// Behaviours declared in the markup, dispatched by the delegated listener
// in main.js. Inline onclick attributes cannot be authorised by a CSP nonce.
registerActions({
'change-view': function (element) {
changeView(element.getAttribute('data-view'));
},
'hide-create-event': hideCreateEventModal,
'go-tryout-match': goToTryoutMatch,
'go-team-match': goToTeamMatch,
'go-create-tryout': goToCreateTryout,
'hide-event-modal': hideEventModal,
});
</script>
{% endblock %}
{% endblock %}
+43 -14
View File
@@ -1,22 +1,25 @@
{% extends "layouts/base.html" %}
{% block title %}Manage Availability - TryoutPro{% endblock %}
{% block page_title %}Manage Availability{% endblock %}
{% block title %}{{ _('Manage Availability') }} - UdeS team manager{% endblock %}
{% block page_title %}{{ _('Manage Availability') }}{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / Coach Availability</span>{% endblock %}
{% block content %}
<div class="card">
<div class="card-header">
<h3><i class="fas fa-clock"></i> Set Your Weekly Availability</h3>
<p class="text-muted small">Select time slots when you're available for One on One sessions</p>
<h3><i class="fas fa-clock"></i> {{ _('Set Your Weekly Availability') }}</h3>
<p class="text-muted small">{{ _('Select time slots when you\'re available for One on One sessions') }}</p>
</div>
<div class="card-body">
<div class="availability-grid" id="availability-grid">
<p class="text-muted">Loading availability grid...</p>
<p class="text-muted">{{ _('Loading availability grid...') }}</p>
</div>
<div class="form-actions mt-4">
<button type="button" class="btn btn-secondary" onclick="clearAllAvailability()">
<i class="fas fa-trash"></i> Clear All
<button type="button" class="btn btn-primary" data-action="save-availability">
<i class="fas fa-save"></i> {{ _('Save Availability') }}
</button>
<button type="button" class="btn btn-secondary" data-action="clear-availability">
<i class="fas fa-trash"></i> {{ _('Clear All') }}
</button>
</div>
</div>
@@ -88,7 +91,7 @@
}
}
</style>
<script>
<script nonce="{{ csp_nonce }}">
// Time slots from 8:00 AM to 10:00 PM (30-minute intervals)
const TIME_SLOTS = [];
for (let h = 8; h <= 22; h++) {
@@ -134,7 +137,7 @@ function renderGrid() {
TIME_SLOTS.forEach(slot => {
const isSelected = selectedSlots[dayIndex] && selectedSlots[dayIndex].includes(slot.time);
const cssClass = isSelected ? 'time-slot selected' : 'time-slot';
html += '<div class="' + cssClass + '" data-day="' + dayIndex + '" data-time="' + slot.time + '" onclick="toggleSlot(' + dayIndex + ', \'' + slot.time + '\', this)">' + slot.display + '</div>';
html += '<div class="' + cssClass + '" data-day="' + dayIndex + '" data-time="' + slot.time + '" data-action="toggle-slot">' + slot.display + '</div>';
});
html += '</div>';
@@ -143,7 +146,9 @@ function renderGrid() {
grid.innerHTML = html;
}
function toggleSlot(dayOfWeek, timeStr, element) {
function toggleSlot(element) {
const dayOfWeek = Number(element.getAttribute('data-day'));
const timeStr = element.getAttribute('data-time');
if (!selectedSlots[dayOfWeek]) {
selectedSlots[dayOfWeek] = [];
}
@@ -168,13 +173,18 @@ function saveAvailability() {
fetch('{{ url_for("users.manage_coach_availability") }}', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': '{{ csrf_token() }}'
},
body: JSON.stringify({ slots: slots })
})
.then(response => response.json())
.then(data => {
if (data.success) {
flash('Availability saved!', 'success');
} else {
flash(data.error || 'Error saving availability.', 'danger');
}
})
.catch(function(error) {
@@ -188,7 +198,10 @@ function clearAllAvailability() {
return;
}
fetch('{{ url_for("users.clear_coach_availability") }}', { method: 'POST' })
fetch('{{ url_for("users.clear_coach_availability") }}', {
method: 'POST',
headers: { 'X-CSRFToken': '{{ csrf_token() }}' }
})
.then(response => response.json())
.then(data => {
if (data.success) {
@@ -203,7 +216,14 @@ function flash(message, type) {
const flashContainer = document.querySelector('.flash-messages');
const alert = document.createElement('div');
alert.className = 'alert alert-' + type + ' alert-dismissible';
alert.innerHTML = '<span>' + message + '</span><button type="button" class="alert-close" onclick="this.parentElement.remove()">&times;</button>';
const text = document.createElement('span');
text.textContent = message;
const close = document.createElement('button');
close.type = 'button';
close.className = 'alert-close';
close.dataset.action = 'remove-element';
close.textContent = '×';
alert.append(text, close);
flashContainer.appendChild(alert);
}
@@ -215,5 +235,14 @@ document.addEventListener('click', function(e) {
saveTimeout = setTimeout(saveAvailability, 1000);
}
});
// Behaviours are declared in the markup with data-action / data-change and
// dispatched by the delegated listener in main.js. This replaces inline
// onclick attributes, which no CSP nonce is able to authorise.
registerActions({
'save-availability': saveAvailability,
'clear-availability': clearAllAvailability,
'toggle-slot': toggleSlot,
});
</script>
{% endblock %}
{% endblock %}
+38 -28
View File
@@ -1,12 +1,12 @@
{% extends "layouts/base.html" %}
{% block title %}Contracts - TryoutPro{% endblock %}
{% block page_title %}Contracts{% endblock %}
{% block title %}{{ _('Contracts') }} - UdeS team manager{% endblock %}
{% block page_title %}{{ _('Contracts') }}{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / <a href="{{ url_for('users.profile') }}">Profile</a> / Contracts</span>{% endblock %}
{% block header_actions %}
{% if current_user.role in ['admin', 'manager', 'coach'] %}
<a href="{{ url_for('users.upload_contract') }}" class="btn btn-primary">
<i class="fas fa-upload"></i> Upload Contract
<i class="fas fa-upload"></i> {{ _('Upload Contract') }}
</a>
{% endif %}
{% endblock %}
@@ -14,7 +14,7 @@
{% block content %}
<div class="card">
<div class="card-header">
<h3><i class="fas fa-file-contract"></i> Contract Dropbox</h3>
<h3><i class="fas fa-file-contract"></i> {{ _('Contract Dropbox') }}</h3>
</div>
<div class="card-body">
{% if contracts %}
@@ -22,12 +22,12 @@
<table class="table">
<thead>
<tr>
<th>Player</th>
<th>Team</th>
<th>Contract</th>
<th>Status</th>
<th>Uploaded</th>
<th>Actions</th>
<th>{{ _('Player') }}</th>
<th>{{ _('Team') }}</th>
<th>{{ _('Contract') }}</th>
<th>{{ _('Status') }}</th>
<th>{{ _('Uploaded') }}</th>
<th>{{ _('Actions') }}</th>
</tr>
</thead>
<tbody>
@@ -46,17 +46,17 @@
<td>{{ contract.uploaded_at.strftime('%Y-%m-%d %H:%M') }}</td>
<td>
<div class="btn-group">
<a href="{{ url_for('users.download_contract', contract_id=contract.id) }}" class="btn btn-sm btn-primary" title="Download">
<i class="fas fa-download"></i> Download
<a href="{{ url_for('users.download_contract', contract_id=contract.id) }}" class="btn btn-sm btn-primary" title="{{ _('Download') }}">
<i class="fas fa-download"></i> {{ _('Download') }}
</a>
{% if contract.signed_file_path %}
<a href="{{ url_for('users.download_signed_contract', contract_id=contract.id) }}" class="btn btn-sm btn-success" title="Download Signed">
<i class="fas fa-file-signature"></i> Signed
<a href="{{ url_for('users.download_signed_contract', contract_id=contract.id) }}" class="btn btn-sm btn-success" title="{{ _('Download Signed') }}">
<i class="fas fa-file-signature"></i> {{ _('Signed') }}
</a>
{% endif %}
{% if current_user.role == 'player' and contract.status == 'pending' %}
<button class="btn btn-sm btn-warning" onclick="showUploadSignedForm({{ contract.id }})" title="Upload Signed Contract">
<i class="fas fa-upload"></i> Return Signed
<button class="btn btn-sm btn-warning" data-action="show-upload-signed" data-contract-id="{{ contract.id }}" title="{{ _('Upload Signed Contract') }}">
<i class="fas fa-upload"></i> {{ _('Return Signed') }}
</button>
{% endif %}
</div>
@@ -69,11 +69,11 @@
{% else %}
<div class="empty-state">
<i class="fas fa-file-contract"></i>
<h4>No contracts found</h4>
<h4>{{ _('No contracts found') }}</h4>
{% if current_user.role == 'player' %}
<p>No contracts have been uploaded for you yet. Contact your coach or manager.</p>
<p>{{ _('No contracts have been uploaded for you yet. Contact your coach or manager.') }}</p>
{% else %}
<p>No contracts have been uploaded yet. Upload a contract using the button above.</p>
<p>{{ _('No contracts have been uploaded yet. Upload a contract using the button above.') }}</p>
{% endif %}
</div>
{% endif %}
@@ -83,23 +83,23 @@
{% if current_user.role == 'player' %}
<!-- Upload Signed Contract Modal -->
<div id="uploadSignedModal" class="modal hidden">
<div class="modal-backdrop" onclick="hideUploadSignedForm()"></div>
<div class="modal-backdrop" data-action="hide-upload-signed"></div>
<div class="modal-content">
<div class="modal-header">
<h3>Upload Signed Contract</h3>
<button class="modal-close" onclick="hideUploadSignedForm()">&times;</button>
<h3>{{ _('Upload Signed Contract') }}</h3>
<button class="modal-close" data-action="hide-upload-signed">&times;</button>
</div>
<div class="modal-body">
<form id="uploadSignedForm" method="POST" enctype="multipart/form-data" class="form">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="form-group">
<label for="signed_file">Select Signed Contract File</label>
<label for="signed_file">{{ _('Select Signed Contract File') }}</label>
<input type="file" id="signed_file" name="signed_file" accept=".pdf,.doc,.docx,.jpg,.jpeg,.png" required>
<small class="form-text text-muted">Accepted formats: PDF, DOC, DOCX, JPG, PNG</small>
<small class="form-text text-muted">{{ _('Accepted formats: PDF, DOC, DOCX, JPG, PNG') }}</small>
</div>
<div class="form-actions">
<button type="button" class="btn btn-secondary" onclick="hideUploadSignedForm()">Cancel</button>
<button type="submit" class="btn btn-primary">Upload Signed Contract</button>
<button type="button" class="btn btn-secondary" data-action="hide-upload-signed">{{ _('Cancel') }}</button>
<button type="submit" class="btn btn-primary">{{ _('Upload Signed Contract') }}</button>
</div>
</form>
</div>
@@ -107,7 +107,7 @@
</div>
{% endif %}
<script>
<script nonce="{{ csp_nonce }}">
function showUploadSignedForm(contractId) {
document.getElementById('uploadSignedForm').action = '/users/contracts/' + contractId + '/upload_signed';
document.getElementById('uploadSignedModal').classList.remove('hidden');
@@ -116,5 +116,15 @@ function showUploadSignedForm(contractId) {
function hideUploadSignedForm() {
document.getElementById('uploadSignedModal').classList.add('hidden');
}
// Behaviours are declared in the markup with data-action / data-change and
// dispatched by the delegated listener in main.js. This replaces inline
// onclick attributes, which no CSP nonce is able to authorise.
registerActions({
'show-upload-signed': function (element) {
showUploadSignedForm(element.getAttribute('data-contract-id'));
},
'hide-upload-signed': hideUploadSignedForm,
});
</script>
{% endblock %}
{% endblock %}
+15 -15
View File
@@ -1,6 +1,6 @@
{% extends "layouts/base.html" %}
{% block title %}Create User - TryoutPro{% endblock %}
{% block page_title %}Create User{% endblock %}
{% block title %}{{ _('Create User') }} - UdeS team manager{% 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 %}
@@ -10,25 +10,25 @@
<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>
<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>
<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>
<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">
<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>
<label for="role">{{ _('Role') }}</label>
<select id="role" name="role" required>
{% for r in roles %}
<option value="{{ r }}">{{ r | capitalize }}</option>
@@ -38,15 +38,15 @@
</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">
<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>
<button type="submit" class="btn btn-primary">{{ _('Create User') }}</button>
</div>
</form>
</div>
</div>
{% endblock %}
{% endblock %}
+37 -37
View File
@@ -1,6 +1,6 @@
{% extends "layouts/base.html" %}
{% block title %}Dashboard - TryoutPro{% endblock %}
{% block page_title %}Dashboard{% endblock %}
{% block title %}{{ _('Dashboard') }} - UdeS team manager{% endblock %}
{% block page_title %}{{ _('Dashboard') }}{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / Dashboard</span>{% endblock %}
{% block content %}
@@ -13,7 +13,7 @@
</div>
<div class="stat-info">
<h3>{{ stats.total_users }}</h3>
<p>Total Users</p>
<p>{{ _('Total Users') }}</p>
</div>
</div>
<div class="stat-card">
@@ -22,7 +22,7 @@
</div>
<div class="stat-info">
<h3>{{ stats.total_players }}</h3>
<p>Players</p>
<p>{{ _('Players') }}</p>
</div>
</div>
<div class="stat-card">
@@ -31,7 +31,7 @@
</div>
<div class="stat-info">
<h3>{{ stats.total_tryouts }}</h3>
<p>Total Tryouts</p>
<p>{{ _('Total Tryouts') }}</p>
</div>
</div>
<div class="stat-card">
@@ -40,7 +40,7 @@
</div>
<div class="stat-info">
<h3>{{ stats.total_evaluations }}</h3>
<p>Evaluations</p>
<p>{{ _('Evaluations') }}</p>
</div>
</div>
<div class="stat-card">
@@ -49,7 +49,7 @@
</div>
<div class="stat-info">
<h3>{{ stats.active_tryouts }}</h3>
<p>Active Tryouts</p>
<p>{{ _('Active Tryouts') }}</p>
</div>
</div>
<div class="stat-card">
@@ -58,7 +58,7 @@
</div>
<div class="stat-info">
<h3>{{ stats.upcoming_tryouts }}</h3>
<p>Upcoming</p>
<p>{{ _('Upcoming') }}</p>
</div>
</div>
</div>
@@ -66,12 +66,12 @@
<div class="dashboard-grid">
<div class="card">
<div class="card-header">
<h3><i class="fas fa-user-plus"></i> Recent Users</h3>
<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>
<tr><th>{{ _('Name') }}</th><th>{{ _('Role') }}</th><th>{{ _('Joined') }}</th></tr>
</thead>
<tbody>
{% for u in stats.recent_users %}
@@ -87,12 +87,12 @@
</div>
<div class="card">
<div class="card-header">
<h3><i class="fas fa-calendar-alt"></i> Recent Tryouts</h3>
<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>
<tr><th>{{ _('Title') }}</th><th>{{ _('Date') }}</th><th>{{ _('Status') }}</th></tr>
</thead>
<tbody>
{% for t in stats.recent_tryouts %}
@@ -111,11 +111,11 @@
{% if stats.upcoming_matches %}
<div class="card mt-4">
<div class="card-header">
<h3><i class="fas fa-futbol"></i> Upcoming Matches</h3>
<h3><i class="fas fa-futbol"></i> {{ _('Upcoming Matches') }}</h3>
</div>
<div class="card-body">
<table class="table">
<thead><tr><th>Match</th><th>Tryout</th><th>Date & Time</th><th>Status</th></tr></thead>
<thead><tr><th>{{ _('Match') }}</th><th>{{ _('Tryout') }}</th><th>{{ _('Date & Time') }}</th><th>{{ _('Status') }}</th></tr></thead>
<tbody>
{% for match in stats.upcoming_matches %}
<tr>
@@ -142,7 +142,7 @@
</div>
<div class="stat-info">
<h3>{{ stats.total_tryouts }}</h3>
<p>My Tryouts</p>
<p>{{ _('My Tryouts') }}</p>
</div>
</div>
<div class="stat-card">
@@ -151,7 +151,7 @@
</div>
<div class="stat-info">
<h3>{{ stats.active_tryouts }}</h3>
<p>Active</p>
<p>{{ _('Active') }}</p>
</div>
</div>
<div class="stat-card">
@@ -160,19 +160,19 @@
</div>
<div class="stat-info">
<h3>{{ stats.total_evaluations }}</h3>
<p>My Evaluations</p>
<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>
<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>
<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 %}
@@ -195,11 +195,11 @@
{% if stats.upcoming_matches %}
<div class="card mt-4">
<div class="card-header">
<h3><i class="fas fa-futbol"></i> Upcoming Matches</h3>
<h3><i class="fas fa-futbol"></i> {{ _('Upcoming Matches') }}</h3>
</div>
<div class="card-body">
<table class="table">
<thead><tr><th>Match</th><th>Tryout</th><th>Date & Time</th><th>Status</th></tr></thead>
<thead><tr><th>{{ _('Match') }}</th><th>{{ _('Tryout') }}</th><th>{{ _('Date & Time') }}</th><th>{{ _('Status') }}</th></tr></thead>
<tbody>
{% for match in stats.upcoming_matches %}
<tr>
@@ -226,7 +226,7 @@
</div>
<div class="stat-info">
<h3>{{ stats.my_evaluations }}</h3>
<p>Evaluations Done</p>
<p>{{ _('Evaluations Done') }}</p>
</div>
</div>
<div class="stat-card">
@@ -235,18 +235,18 @@
</div>
<div class="stat-info">
<h3>{{ stats.pending_evaluations }}</h3>
<p>Pending</p>
<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>
<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>
<tr><th>{{ _('Player') }}</th><th>{{ _('Tryout') }}</th><th>{{ _('Score') }}</th><th>{{ _('Date') }}</th></tr>
</thead>
<tbody>
{% for e in stats.my_recent_evaluations %}
@@ -270,19 +270,19 @@
</div>
<div class="stat-info">
<h3>{{ stats.my_tryouts }}</h3>
<p>My Tryouts</p>
<p>{{ _('My Tryouts') }}</p>
</div>
</div>
</div>
<div class="dashboard-grid">
<div class="card">
<div class="card-header">
<h3><i class="fas fa-futbol"></i> My Next Matches</h3>
<h3><i class="fas fa-futbol"></i> {{ _('My Next Matches') }}</h3>
</div>
<div class="card-body">
{% if stats.next_matches %}
<table class="table">
<thead><tr><th>Tryout</th><th>Match</th><th>Date & Time</th><th>Opponent</th></tr></thead>
<thead><tr><th>{{ _('Tryout') }}</th><th>{{ _('Match') }}</th><th>{{ _('Date & Time') }}</th><th>{{ _('Opponent') }}</th></tr></thead>
<tbody>
{% for item in stats.next_matches %}
<tr>
@@ -308,17 +308,17 @@
</tbody>
</table>
{% else %}
<p class="text-muted">No upcoming matches yet.</p>
<p class="text-muted">{{ _('No upcoming matches yet.') }}</p>
{% endif %}
</div>
</div>
<div class="card">
<div class="card-header">
<h3><i class="fas fa-clipboard-list"></i> My Registrations</h3>
<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>
<thead><tr><th>{{ _('Tryout') }}</th><th>{{ _('Date') }}</th><th>{{ _('Status') }}</th></tr></thead>
<tbody>
{% for r in stats.my_registrations %}
<tr>
@@ -341,7 +341,7 @@
</div>
<div class="stat-info">
<h3>{{ stats.total_players }}</h3>
<p>Total Players</p>
<p>{{ _('Total Players') }}</p>
</div>
</div>
<div class="stat-card">
@@ -350,17 +350,17 @@
</div>
<div class="stat-info">
<h3>{{ stats.total_evaluations }}</h3>
<p>Total Evaluations</p>
<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>
<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>
<thead><tr><th>#</th><th>{{ _('Player') }}</th><th>{{ _('Avg Score') }}</th></tr></thead>
<tbody>
{% for player, score in stats.top_players %}
<tr>
@@ -370,7 +370,7 @@
</tr>
{% endfor %}
{% if not stats.top_players %}
<tr><td colspan="3" class="text-center">No evaluations yet.</td></tr>
<tr><td colspan="3" class="text-center">{{ _('No evaluations yet.') }}</td></tr>
{% endif %}
</tbody>
</table>
@@ -378,4 +378,4 @@
</div>
{% endif %}
</div>
{% endblock %}
{% endblock %}
+30 -31
View File
@@ -1,6 +1,6 @@
{% extends "layouts/base.html" %}
{% block title %}Edit Profile - TryoutPro{% endblock %}
{% block page_title %}Edit Profile{% endblock %}
{% block title %}{{ _('Edit Profile') }} - UdeS team manager{% 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 %}
@@ -10,31 +10,31 @@
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="form-row">
<div class="form-group col-6">
<label for="username">Username</label>
<label for="username">{{ _('Username') }}</label>
<input type="text" id="username" name="username" value="{{ user.username }}" required>
</div>
<div class="form-group col-6">
<label for="full_name">Full Name</label>
<label for="full_name">{{ _('Full Name') }}</label>
<input type="text" id="full_name" name="full_name" value="{{ user.full_name }}" required>
</div>
</div>
<div class="form-row">
<div class="form-group col-6">
<label for="email">Email</label>
<label for="email">{{ _('Email') }}</label>
<input type="email" id="email" name="email" value="{{ user.email }}" required>
</div>
<div class="form-group col-6">
<label for="phone">Phone</label>
<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>
<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>
<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() %}
@@ -44,14 +44,14 @@
</label>
{% endfor %}
</div>
<small class="form-text text-muted">Select all games you're signing in for.</small>
<small class="form-text text-muted">{{ _('Select all games you\'re signing in for.') }}</small>
</div>
<!-- Gamertag inputs - will be shown when game is selected -->
<div id="gamertag-section" style="display: none;">
<hr class="section-divider">
<h4 class="section-title"><i class="fas fa-chart-line"></i> Gamertags for TRN</h4>
<p class="text-muted small">Enter your gamertag for each selected game to link to your Tracker Network profile.</p>
<h4 class="section-title"><i class="fas fa-chart-line"></i> {{ _('Gamertags for TRN') }}</h4>
<p class="text-muted small">{{ _('Enter your gamertag for each selected game to link to your Tracker Network profile.') }}</p>
{% for game in esport_games %}
{% set gamertag_data = user_gamertags.get(game) %}
@@ -59,14 +59,14 @@
<h5>{{ game }}</h5>
<div class="form-row">
<div class="form-group col-6">
<label for="gamertag_{{ game }}">Gamertag</label>
<label for="gamertag_{{ game }}">{{ _('Gamertag') }}</label>
<input type="text" id="gamertag_{{ game }}" name="gamertag_{{ game }}" value="{{ gamertag_data.gamertag if gamertag_data else '' }}" placeholder="Your {{ game }} gamertag">
</div>
{% if game_platforms.get(game) %}
<div class="form-group col-6">
<label for="platform_{{ game }}">Platform</label>
<label for="platform_{{ game }}">{{ _('Platform') }}</label>
<select id="platform_{{ game }}" name="platform_{{ game }}">
<option value="">Select Platform</option>
<option value="">{{ _('Select Platform') }}</option>
{% for platform in game_platforms[game] %}
<option value="{{ platform }}" {% if gamertag_data and gamertag_data.platform == platform %}selected{% endif %}>{{ platform }}</option>
{% endfor %}
@@ -79,40 +79,39 @@
</div>
<div class="form-row">
<div class="form-group col-6">
<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-6">
<label for="discord_user_id"><i class="fab fa-discord"></i> Discord User ID <small>(for DMs)</small></label>
<input type="text" id="discord_user_id" name="discord_user_id" value="{{ user.discord_user_id or '' }}" placeholder="Numeric ID (e.g. 123456789012345678)">
<small class="text-muted">Enable Developer Mode in Discord → Right-click profile → Copy ID</small>
<div class="form-group col-12">
<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') }}">
<a href="{{ url_for('auth.discord_login') }}" class="btn btn-secondary mt-2">
<i class="fab fa-discord"></i>
{% if user.discord_user_id %}{{ _('Reconnect') }}{% else %}{{ _('Connect Discord Account') }}{% endif %}
</a>
</div>
</div>
<div class="form-row">
<div class="form-group col-6">
<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">
<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>
<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">
<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>
<button type="submit" class="btn btn-primary">{{ _('Save Changes') }}</button>
</div>
</form>
</div>
</div>
<script>
<script nonce="{{ csp_nonce }}">
var GAME_PLATFORMS = {{ game_platforms|tojson }};
// Show/hide gamertag inputs when games are checked
+26 -26
View File
@@ -1,6 +1,6 @@
{% extends "layouts/base.html" %}
{% block title %}Edit {{ user.username }} - TryoutPro{% endblock %}
{% block page_title %}Edit User{% endblock %}
{% block title %}Edit {{ user.username }} - UdeS team manager{% 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 %}
@@ -10,21 +10,21 @@
<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>
<label for="full_name">{{ _('Full Name') }}</label>
<input type="text" id="full_name" name="full_name" value="{{ user.username }}" required>
</div>
<div class="form-group col-6">
<label for="email">Email</label>
<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>
<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>
<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>
@@ -40,10 +40,10 @@
</div>
<hr class="section-divider">
<h4 class="section-title"><i class="fas fa-gamepad"></i> E-Sports Profile</h4>
<h4 class="section-title"><i class="fas fa-gamepad"></i> {{ _('E-Sports Profile') }}</h4>
<div class="form-group">
<label>Games</label>
<label>{{ _('Games') }}</label>
<div class="checkbox-grid">
{% for game in esport_games %}
{% set games_list = user.get_games_list() %}
@@ -58,8 +58,8 @@
<!-- Gamertag inputs - will be shown when game is selected -->
<div id="gamertag-section" style="display: none;">
<hr class="section-divider">
<h4 class="section-title"><i class="fas fa-chart-line"></i> Gamertags for TRN</h4>
<p class="text-muted small">Enter gamertag for each selected game to link to Tracker Network.</p>
<h4 class="section-title"><i class="fas fa-chart-line"></i> {{ _('Gamertags for TRN') }}</h4>
<p class="text-muted small">{{ _('Enter gamertag for each selected game to link to Tracker Network.') }}</p>
{% for game in esport_games %}
{% set gamertag_data = user_gamertags.get(game) %}
@@ -67,14 +67,14 @@
<h5>{{ game }}</h5>
<div class="form-row">
<div class="form-group col-6">
<label for="gamertag_{{ game }}">Gamertag</label>
<input type="text" id="gamertag_{{ game }}" name="gamertag_{{ game }}" value="{{ gamertag_data.gamertag if gamertag_data else '' }}" placeholder="Gamertag">
<label for="gamertag_{{ game }}">{{ _('Gamertag') }}</label>
<input type="text" id="gamertag_{{ game }}" name="gamertag_{{ game }}" value="{{ gamertag_data.gamertag if gamertag_data else '' }}" placeholder="{{ _('Gamertag') }}">
</div>
{% if game_platforms.get(game) %}
<div class="form-group col-6">
<label for="platform_{{ game }}">Platform</label>
<label for="platform_{{ game }}">{{ _('Platform') }}</label>
<select id="platform_{{ game }}" name="platform_{{ game }}">
<option value="">Select Platform</option>
<option value="">{{ _('Select Platform') }}</option>
{% for platform in game_platforms[game] %}
<option value="{{ platform }}" {% if gamertag_data and gamertag_data.platform == platform %}selected{% endif %}>{{ platform }}</option>
{% endfor %}
@@ -88,33 +88,33 @@
<div class="form-row">
<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">
<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="discord_user_id"><i class="fab fa-discord"></i> Discord User ID <small>(for DMs)</small></label>
<input type="text" id="discord_user_id" name="discord_user_id" value="{{ user.discord_user_id or '' }}" placeholder="Numeric ID (e.g. 123456789012345678)">
<small class="text-muted">Enable Developer Mode in Discord → Right-click profile → Copy ID</small>
<label for="discord_user_id"><i class="fab fa-discord"></i> Discord User ID <small>{{ _('(for DMs)') }}</small></label>
<input type="text" id="discord_user_id" name="discord_user_id" value="{{ user.discord_user_id or '' }}" placeholder="{{ _('Numeric ID (e.g. 123456789012345678)') }}">
<small class="text-muted">{{ _('Enable Developer Mode in Discord → Right-click profile → Copy ID') }}</small>
</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">
<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>
<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">
<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>
<button type="submit" class="btn btn-primary">{{ _('Update User') }}</button>
</div>
</form>
</div>
</div>
<script>
<script nonce="{{ csp_nonce }}">
// Show/hide gamertag inputs when games are checked
function toggleGamertagInputs() {
var selectedGames = [];
@@ -148,4 +148,4 @@ document.addEventListener('DOMContentLoaded', function() {
toggleGamertagInputs();
});
</script>
{% endblock %}
{% endblock %}
+40 -40
View File
@@ -1,5 +1,5 @@
{% extends "layouts/base.html" %}
{% block title %}Evaluate {{ player.username }} - TryoutPro{% endblock %}
{% block title %}Evaluate {{ player.username }} - UdeS team manager{% endblock %}
{% block page_title %}Evaluate {{ player.username }}{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / <a href="{{ url_for('tryouts.view_tryout', tryout_id=tryout.id) }}">{{ tryout.title }}</a> / Evaluate</span>{% endblock %}
@@ -7,7 +7,7 @@
<div class="dashboard-grid">
<div class="card">
<div class="card-header">
<h3><i class="fas fa-clipboard-list"></i> Player Evaluation</h3>
<h3><i class="fas fa-clipboard-list"></i> {{ _('Player Evaluation') }}</h3>
<span class="badge badge-info">{{ tryout.title }} - {{ tryout.game }}</span>
</div>
<div class="card-body">
@@ -21,7 +21,7 @@
{% 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.
<i class="fas fa-info-circle"></i> {{ _('You have already evaluated this player. Your previous scores are shown below.') }}
</div>
{% endif %}
@@ -30,23 +30,23 @@
<div class="form-row">
<div class="form-group col-4">
<label for="mecanics_score">Mecanics (1-10)</label>
<label for="mecanics_score">{{ _('Mecanics (1-10)') }}</label>
<div class="score-input">
<input type="range" id="mecanics_score" name="mecanics_score" min="1" max="10" value="{{ existing_eval.mecanics_score or 5 }}" oninput="this.nextElementSibling.textContent = this.value">
<input type="range" id="mecanics_score" name="mecanics_score" min="1" max="10" value="{{ existing_eval.mecanics_score or 5 }}" data-mirror>
<span class="range-value">{{ existing_eval.mecanics_score or 5 }}</span>
</div>
</div>
<div class="form-group col-4">
<label for="cohesion_score">Cohesion (1-10)</label>
<label for="cohesion_score">{{ _('Cohesion (1-10)') }}</label>
<div class="score-input">
<input type="range" id="cohesion_score" name="cohesion_score" min="1" max="10" value="{{ existing_eval.cohesion_score or 5 }}" oninput="this.nextElementSibling.textContent = this.value">
<input type="range" id="cohesion_score" name="cohesion_score" min="1" max="10" value="{{ existing_eval.cohesion_score or 5 }}" data-mirror>
<span class="range-value">{{ existing_eval.cohesion_score or 5 }}</span>
</div>
</div>
<div class="form-group col-4">
<label for="communication_score">Communication (1-10)</label>
<label for="communication_score">{{ _('Communication (1-10)') }}</label>
<div class="score-input">
<input type="range" id="communication_score" name="communication_score" min="1" max="10" value="{{ existing_eval.communication_score or 5 }}" oninput="this.nextElementSibling.textContent = this.value">
<input type="range" id="communication_score" name="communication_score" min="1" max="10" value="{{ existing_eval.communication_score or 5 }}" data-mirror>
<span class="range-value">{{ existing_eval.communication_score or 5 }}</span>
</div>
</div>
@@ -54,23 +54,23 @@
<div class="form-row">
<div class="form-group col-4">
<label for="gamesense_score">Gamesense (1-10)</label>
<label for="gamesense_score">{{ _('Gamesense (1-10)') }}</label>
<div class="score-input">
<input type="range" id="gamesense_score" name="gamesense_score" min="1" max="10" value="{{ existing_eval.gamesense_score or 5 }}" oninput="this.nextElementSibling.textContent = this.value">
<input type="range" id="gamesense_score" name="gamesense_score" min="1" max="10" value="{{ existing_eval.gamesense_score or 5 }}" data-mirror>
<span class="range-value">{{ existing_eval.gamesense_score or 5 }}</span>
</div>
</div>
<div class="form-group col-4">
<label for="versatility_score">Versatility (1-10)</label>
<label for="versatility_score">{{ _('Versatility (1-10)') }}</label>
<div class="score-input">
<input type="range" id="versatility_score" name="versatility_score" min="1" max="10" value="{{ existing_eval.versatility_score or 5 }}" oninput="this.nextElementSibling.textContent = this.value">
<input type="range" id="versatility_score" name="versatility_score" min="1" max="10" value="{{ existing_eval.versatility_score or 5 }}" data-mirror>
<span class="range-value">{{ existing_eval.versatility_score or 5 }}</span>
</div>
</div>
<div class="form-group col-4">
<label for="discipline_score">Discipline (1-10)</label>
<label for="discipline_score">{{ _('Discipline (1-10)') }}</label>
<div class="score-input">
<input type="range" id="discipline_score" name="discipline_score" min="1" max="10" value="{{ existing_eval.discipline_score or 5 }}" oninput="this.nextElementSibling.textContent = this.value">
<input type="range" id="discipline_score" name="discipline_score" min="1" max="10" value="{{ existing_eval.discipline_score or 5 }}" data-mirror>
<span class="range-value">{{ existing_eval.discipline_score or 5 }}</span>
</div>
</div>
@@ -78,23 +78,23 @@
<div class="form-row">
<div class="form-group col-4">
<label for="analysis_score">Analysis (1-10)</label>
<label for="analysis_score">{{ _('Analysis (1-10)') }}</label>
<div class="score-input">
<input type="range" id="analysis_score" name="analysis_score" min="1" max="10" value="{{ existing_eval.analysis_score or 5 }}" oninput="this.nextElementSibling.textContent = this.value">
<input type="range" id="analysis_score" name="analysis_score" min="1" max="10" value="{{ existing_eval.analysis_score or 5 }}" data-mirror>
<span class="range-value">{{ existing_eval.analysis_score or 5 }}</span>
</div>
</div>
<div class="form-group col-4">
<label for="sport_ethics_score">Sport Ethics (1-10)</label>
<label for="sport_ethics_score">{{ _('Sport Ethics (1-10)') }}</label>
<div class="score-input">
<input type="range" id="sport_ethics_score" name="sport_ethics_score" min="1" max="10" value="{{ existing_eval.sport_ethics_score or 5 }}" oninput="this.nextElementSibling.textContent = this.value">
<input type="range" id="sport_ethics_score" name="sport_ethics_score" min="1" max="10" value="{{ existing_eval.sport_ethics_score or 5 }}" data-mirror>
<span class="range-value">{{ existing_eval.sport_ethics_score or 5 }}</span>
</div>
</div>
<div class="form-group col-4">
<label for="mental_score">Mental (1-10)</label>
<label for="mental_score">{{ _('Mental (1-10)') }}</label>
<div class="score-input">
<input type="range" id="mental_score" name="mental_score" min="1" max="10" value="{{ existing_eval.mental_score or 5 }}" oninput="this.nextElementSibling.textContent = this.value">
<input type="range" id="mental_score" name="mental_score" min="1" max="10" value="{{ existing_eval.mental_score or 5 }}" data-mirror>
<span class="range-value">{{ existing_eval.mental_score or 5 }}</span>
</div>
</div>
@@ -103,23 +103,23 @@
{% set positions = game_positions.get(tryout.game, []) %}
<div class="form-row">
<div class="form-group col-12">
<label for="position_recommendation">Recommended Position</label>
<label for="position_recommendation">{{ _('Recommended Position') }}</label>
{% if positions %}
<select id="position_recommendation" name="position_recommendation" class="form-select">
<option value="">-- Select Position --</option>
<option value="">{{ _('-- Select Position --') }}</option>
{% for pos in positions %}
<option value="{{ pos }}" {% if existing_eval.position_recommendation == pos %}selected{% endif %}>{{ pos }}</option>
{% endfor %}
</select>
{% else %}
<input type="text" id="position_recommendation" name="position_recommendation" value="{{ existing_eval.position_recommendation or '' }}" placeholder="Enter position (optional)">
<input type="text" id="position_recommendation" name="position_recommendation" value="{{ existing_eval.position_recommendation or '' }}" placeholder="{{ _('Enter position (optional)') }}">
{% endif %}
</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>
<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">
@@ -127,8 +127,8 @@
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> {% if existing_eval %}Update Evaluation{% else %}Submit Evaluation{% endif %}
</button>
<a href="{{ url_for('users.notes_dashboard') }}" class="btn btn-outline" title="Add note for this player">
<i class="fas fa-sticky-note"></i> Add Note
<a href="{{ url_for('users.notes_dashboard') }}" class="btn btn-outline" title="{{ _('Add note for this player') }}">
<i class="fas fa-sticky-note"></i> {{ _('Add Note') }}
</a>
</div>
</form>
@@ -144,18 +144,18 @@
<table class="table">
<thead>
<tr>
<th>Evaluator</th>
<th>Mecanics</th>
<th>Cohesion</th>
<th>Communication</th>
<th>Gamesense</th>
<th>Versatility</th>
<th>Discipline</th>
<th>Analysis</th>
<th>Sport Ethics</th>
<th>Mental</th>
<th>Overall</th>
<th>Position</th>
<th>{{ _('Evaluator') }}</th>
<th>{{ _('Mecanics') }}</th>
<th>{{ _('Cohesion') }}</th>
<th>{{ _('Communication') }}</th>
<th>{{ _('Gamesense') }}</th>
<th>{{ _('Versatility') }}</th>
<th>{{ _('Discipline') }}</th>
<th>{{ _('Analysis') }}</th>
<th>{{ _('Sport Ethics') }}</th>
<th>{{ _('Mental') }}</th>
<th>{{ _('Overall') }}</th>
<th>{{ _('Position') }}</th>
</tr>
</thead>
<tbody>
+5 -3
View File
@@ -1,6 +1,7 @@
{% extends "layouts/base.html" %}
{% block title %}Evaluations - TryoutPro{% endblock %}
{% block page_title %}Evaluations{% endblock %}
{% import 'layouts/_pagination.html' as pager %}
{% block title %}{{ _('Evaluations') }} - UdeS team manager{% endblock %}
{% block page_title %}{{ _('Evaluations') }}{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / Evaluations</span>{% endblock %}
{% block content %}
@@ -93,7 +94,7 @@
<td colspan="15" class="text-center">
<div class="empty-state">
<i class="fas fa-clipboard"></i>
<h3>No evaluations yet</h3>
<h3>{{ _('No evaluations yet') }}</h3>
</div>
</td>
</tr>
@@ -101,6 +102,7 @@
</tbody>
</table>
</div>
{{ pager.controls(pagination) }}
</div>
</div>
{% endblock %}
+8 -8
View File
@@ -1,17 +1,17 @@
{% extends "layouts/base.html" %}
{% block title %}Login - TryoutPro{% endblock %}
{% block title %}{{ _('Login') }} - UdeS team manager{% 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>
<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>
<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>
<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 %}
{% endblock %}
+105 -67
View File
@@ -1,5 +1,5 @@
{% extends "layouts/base.html" %}
{% block title %}{% if match %}Edit Match{% else %}Schedule Match{% endif %} - {{ tryout.title }} - TryoutPro{% endblock %}
{% block title %}{% if match %}Edit Match{% else %}Schedule Match{% endif %} - {{ tryout.title }} - UdeS team manager{% endblock %}
{% block page_title %}{% if match %}Edit Match{% else %}Schedule Match{% endif %}{% endblock %}
{% block breadcrumb %}
<span class="breadcrumb">
@@ -31,11 +31,11 @@
{% if not is_practice %}
{% if not match %}
<div class="form-group">
<label for="match_type">Match Type</label>
<select name="match_type" id="match_type" class="form-select" onchange="toggleMatchType()" required>
<option value="team_vs_team">Team vs Team</option>
<option value="player_vs_player">Player vs Player</option>
<option value="player_scrim">Player Scrim</option>
<label for="match_type">{{ _('Match Type') }}</label>
<select name="match_type" id="match_type" class="form-select" data-change="toggle-match-type" required>
<option value="team_vs_team">{{ _('Team vs Team') }}</option>
<option value="player_vs_player">{{ _('Player vs Player') }}</option>
<option value="player_scrim">{{ _('Player Scrim') }}</option>
</select>
</div>
{% else %}
@@ -45,45 +45,45 @@
{% if not is_practice %}
<div class="form-group">
<label for="title">Match Title</label>
<input type="text" name="title" id="title" class="form-input" value="{{ match.title if match else '' }}" placeholder="e.g., Alpha vs Bravo Scrimmage" required>
<label for="title">{{ _('Match Title') }}</label>
<input type="text" name="title" id="title" class="form-input" value="{{ match.title if match else '' }}" placeholder="{{ _('e.g., Alpha vs Bravo Scrimmage') }}" required>
</div>
{% endif %}
<div class="form-row">
<div class="form-group">
<label for="date">Date</label>
<label for="date">{{ _('Date') }}</label>
<input type="date" name="date" id="date" class="form-input" value="{{ match.date.strftime('%Y-%m-%d') if match else (prefill_date if is_practice else tryout.date.strftime('%Y-%m-%d')) }}" required>
</div>
{% if match %}
<div class="form-group">
<label for="status">Status</label>
<label for="status">{{ _('Status') }}</label>
<select name="status" id="status" class="form-select">
<option value="scheduled" {% if match.status == 'scheduled' %}selected{% endif %}>Scheduled</option>
<option value="completed" {% if match.status == 'completed' %}selected{% endif %}>Completed</option>
<option value="cancelled" {% if match.status == 'cancelled' %}selected{% endif %}>Cancelled</option>
<option value="scheduled" {% if match.status == 'scheduled' %}selected{% endif %}>{{ _('Scheduled') }}</option>
<option value="completed" {% if match.status == 'completed' %}selected{% endif %}>{{ _('Completed') }}</option>
<option value="cancelled" {% if match.status == 'cancelled' %}selected{% endif %}>{{ _('Cancelled') }}</option>
</select>
</div>
{% endif %}
<div class="form-group">
<label for="location">Location</label>
<input type="text" name="location" id="location" class="form-input" value="{{ match.location or '' if match else '' }}" placeholder="Match location">
<label for="location">{{ _('Location') }}</label>
<input type="text" name="location" id="location" class="form-input" value="{{ match.location or '' if match else '' }}" placeholder="{{ _('Match location') }}">
</div>
</div>
<!-- Merged Availability Grid -->
{% if current_user.can_manage_teams() or current_user.can_schedule_matches() %}
<hr class="section-divider">
<h4 class="section-title"><i class="fas fa-clock"></i> Select Match Time</h4>
<h4 class="section-title"><i class="fas fa-clock"></i> {{ _('Select Match Time') }}</h4>
<div style="display:flex; justify-content:space-between; align-items:center; margin-bottom:8px;">
<p class="text-muted small" style="margin:0;">Click time slots consecutively to set match duration. Players available in all selected time blocks are shown below.</p>
<button type="button" class="btn btn-sm btn-outline" onclick="clearTimeSelection()" title="Reset time selection">
<i class="fas fa-undo"></i> Reset Time
<p class="text-muted small" style="margin:0;">{{ _('Click time slots consecutively to set match duration. Players available in all selected time blocks are shown below.') }}</p>
<button type="button" class="btn btn-sm btn-outline" data-action="clear-time-selection" title="{{ _('Reset time selection') }}">
<i class="fas fa-undo"></i> {{ _('Reset Time') }}
</button>
</div>
<div id="merged-disponibility-grid" class="merged-disponibility-grid">
<p class="text-muted">Loading...</p>
<p class="text-muted">{{ _('Loading...') }}</p>
</div>
<div class="form-row">
@@ -100,40 +100,40 @@
<!-- Fallback time inputs for users without disponibility access -->
<div class="form-row">
<div class="form-group">
<label for="start_time">Start Time</label>
<label for="start_time">{{ _('Start Time') }}</label>
<input type="time" name="start_time" id="start_time" class="form-input" value="{{ match.start_time.strftime('%H:%M') if match and match.start_time else '' }}">
</div>
<div class="form-group">
<label for="end_time">End Time</label>
<label for="end_time">{{ _('End Time') }}</label>
<input type="time" name="end_time" id="end_time" class="form-input" value="{{ match.end_time.strftime('%H:%M') if match and match.end_time else '' }}">
</div>
</div>
{% endif %}
<div class="form-group">
<label for="description">Description</label>
<textarea name="description" id="description" class="form-textarea" placeholder="Optional notes about this match">{% if match %}{{ match.description or '' }}{% endif %}</textarea>
<label for="description">{{ _('Description') }}</label>
<textarea name="description" id="description" class="form-textarea" placeholder="{{ _('Optional notes about this match') }}">{% if match %}{{ match.description or '' }}{% endif %}</textarea>
</div>
<!-- Team vs Team Selection -->
<div id="team-vs-team-section" {% if match and match.match_type != 'team_vs_team' %}class="hidden"{% endif %}>
<hr class="section-divider">
<h4 class="section-title"><i class="fas fa-users"></i> Select Teams</h4>
<h4 class="section-title"><i class="fas fa-users"></i> {{ _('Select Teams') }}</h4>
<div class="form-row">
<div class="form-group">
<label for="team1_id">Team 1</label>
<label for="team1_id">{{ _('Team 1') }}</label>
<select name="team1_id" id="team1_id" class="form-select">
<option value="">-- Select Team 1 --</option>
<option value="">{{ _('-- Select Team 1 --') }}</option>
{% for team in teams %}
<option value="{{ team.id }}" {% if match and match.team1_id == team.id %}selected{% endif %}>{{ team.name }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label for="team2_id">Team 2</label>
<label for="team2_id">{{ _('Team 2') }}</label>
<select name="team2_id" id="team2_id" class="form-select">
<option value="">-- Select Team 2 --</option>
<option value="">{{ _('-- Select Team 2 --') }}</option>
{% for team in teams %}
<option value="{{ team.id }}" {% if match and match.team2_id == team.id %}selected{% endif %}>{{ team.name }}</option>
{% endfor %}
@@ -154,14 +154,14 @@
<span class="presence-badge {{ 'presence-confirmed' if pdata.attendance_confirmed else 'presence-pending' }}"
data-participant-id="{{ pdata.participant_id }}"
data-match-id="{{ match.id }}"
onclick="togglePresence({{ match.id }}, {{ pdata.participant_id }}, this)"
title="Click to toggle presence">
data-action="toggle-presence" data-match-id="{{ match.id }}" data-participant-id="{{ pdata.participant_id }}"
title="{{ _('Click to toggle presence') }}">
{{ '✅ Confirmed' if pdata.attendance_confirmed else '⏳ Pending' }}
</span>
{% endif %}
</li>
{% else %}
<li class="text-muted">No players assigned</li>
<li class="text-muted">{{ _('No players assigned') }}</li>
{% endfor %}
</ul>
</div>
@@ -180,14 +180,14 @@
<span class="presence-badge {{ 'presence-confirmed' if pdata.attendance_confirmed else 'presence-pending' }}"
data-participant-id="{{ pdata.participant_id }}"
data-match-id="{{ match.id }}"
onclick="togglePresence({{ match.id }}, {{ pdata.participant_id }}, this)"
title="Click to toggle presence">
data-action="toggle-presence" data-match-id="{{ match.id }}" data-participant-id="{{ pdata.participant_id }}"
title="{{ _('Click to toggle presence') }}">
{{ '✅ Confirmed' if pdata.attendance_confirmed else '⏳ Pending' }}
</span>
{% endif %}
</li>
{% else %}
<li class="text-muted">No players assigned</li>
<li class="text-muted">{{ _('No players assigned') }}</li>
{% endfor %}
</ul>
</div>
@@ -198,37 +198,37 @@
<!-- Player vs Player Selection -->
<div id="player-vs-player-section" {% if match and match.match_type != 'player_vs_player' %}class="hidden"{% endif %}>
<hr class="section-divider">
<h4 class="section-title"><i class="fas fa-user-friends"></i> Select Players</h4>
<h4 class="section-title"><i class="fas fa-user-friends"></i> {{ _('Select Players') }}</h4>
<!-- Randomize Teams Section -->
<div class="randomize-section">
<div class="randomize-controls">
<label><i class="fas fa-random"></i> Randomize Teams:</label>
<input type="number" id="team1-size" class="randomize-input" min="1" value="1" onchange="updateRandomizePreview()">
<label><i class="fas fa-random"></i> {{ _('Randomize Teams:') }}</label>
<input type="number" id="team1-size" class="randomize-input" min="1" value="1" data-change="update-randomize-preview">
<span>vs</span>
<span id="team2-size-preview" class="randomize-preview">0</span>
<button type="button" class="btn btn-sm randomize-btn" onclick="randomizeTeams()">
<i class="fas fa-random"></i> Randomize
<button type="button" class="btn btn-sm randomize-btn" data-action="randomize-teams">
<i class="fas fa-random"></i> {{ _('Randomize') }}
</button>
</div>
<p class="text-muted small" id="randomize-hint">Select players then click Randomize to split them into teams.</p>
<p class="text-muted small" id="randomize-hint">{{ _('Select players then click Randomize to split them into teams.') }}</p>
</div>
<div class="pvp-layout">
<div class="team-column">
<h5 class="team-header">Team 1</h5>
<h5 class="team-header">{{ _('Team 1') }}</h5>
<div id="team1-selection" class="team-selection"></div>
</div>
<div class="player-pool">
<div class="player-pool-header">Available Players</div>
<div id="available-players-pool" class="available-players-pool">
<p class="text-muted small">All registered players are shown. Click time slots to filter available players.</p>
<p class="text-muted small">{{ _('All registered players are shown. Click time slots to filter available players.') }}</p>
</div>
</div>
<div class="team-column">
<h5 class="team-header">Team 2</h5>
<h5 class="team-header">{{ _('Team 2') }}</h5>
<div id="team2-selection" class="team-selection"></div>
</div>
</div>
@@ -240,10 +240,10 @@
<!-- Player Scrim Selection -->
<div id="player-scrim-section" {% if match and match.match_type != 'player_scrim' %}class="hidden"{% endif %}>
<hr class="section-divider">
<h4 class="section-title"><i class="fas fa-users"></i> Select Players</h4>
<h4 class="section-title"><i class="fas fa-users"></i> {{ _('Select Players') }}</h4>
{% if current_user.can_manage_teams() or current_user.can_schedule_matches() %}
<p class="text-muted small"><i class="fas fa-info-circle"></i> Green indicators show player availability for the match date/time</p>
<p class="text-muted small"><i class="fas fa-info-circle"></i> {{ _('Green indicators show player availability for the match date/time') }}</p>
{% endif %}
<div class="checkbox-grid" id="scrim-players-list">
@@ -262,7 +262,7 @@
<i class="fas fa-save"></i> {% if match %}Save Changes{% else %}Schedule Match{% endif %}
</button>
<a href="{{ url_for('tryouts.view_tryout', tryout_id=tryout.id) }}" class="btn btn-secondary">
<i class="fas fa-times"></i> Cancel
<i class="fas fa-times"></i> {{ _('Cancel') }}
</a>
</div>
</form>
@@ -445,16 +445,23 @@
gap: 4px;
}
</style>
<script>
<script nonce="{{ csp_nonce }}">
// Player data as simple JS object (id -> full_name)
var playerDataById = {
player_data: {
{%- for p in all_players %}
{{ p.id }}: "{{ p.username | escape }}",
{{ p.id }}: {{ p.username | tojson }},
{%- endfor %}
}
};
var HTML_ESCAPES = {'&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;'};
function escapeHtml(value) {
return String(value).replace(/[&<>"']/g, function (character) {
return HTML_ESCAPES[character];
});
}
// All registered player IDs
var allRegisteredPlayers = [
{%- for p in all_players %}
@@ -554,8 +561,8 @@ document.addEventListener('DOMContentLoaded', function() {
var teamDiv = document.getElementById('team1-selection');
var playerName = playerDataById.player_data[pid];
if (playerName) {
var html = '<div class="player-item" data-player-id="' + pid + '" onclick="returnToPool(' + pid + ', event)">';
html += playerName;
var html = '<div class="player-item" data-player-id="' + pid + '" data-action="return-to-pool">';
html += escapeHtml(playerName);
html += '<span class="remove-btn">↺</span>';
html += '</div>';
teamDiv.insertAdjacentHTML('beforeend', html);
@@ -567,8 +574,8 @@ document.addEventListener('DOMContentLoaded', function() {
var teamDiv = document.getElementById('team2-selection');
var playerName = playerDataById.player_data[pid];
if (playerName) {
var html = '<div class="player-item" data-player-id="' + pid + '" onclick="returnToPool(' + pid + ', event)">';
html += playerName;
var html = '<div class="player-item" data-player-id="' + pid + '" data-action="return-to-pool">';
html += escapeHtml(playerName);
html += '<span class="remove-btn">↺</span>';
html += '</div>';
teamDiv.insertAdjacentHTML('beforeend', html);
@@ -622,7 +629,7 @@ function renderMergedDisponibilityGrid() {
var jsDay = dateObj.getDay();
var dayOfWeek = jsDay === 0 ? 6 : jsDay - 1;
var html = '<div style="margin-bottom: 10px;"><strong>Click time slots consecutively to set match duration</strong></div>';
var html = '<div style="margin-bottom: 10px;"><strong>{{ _('Click time slots consecutively to set match duration') }}</strong></div>';
DAYS.forEach(function(day) {
if (day.value !== dayOfWeek) return;
@@ -661,7 +668,7 @@ function renderMergedDisponibilityGrid() {
}
dayRow += '<div class="' + cssClass + '" data-day="' + day.value + '" data-time="' + slotData.time + '" ' +
'onclick="toggleTimeSlot(' + day.value + ', \'' + slotData.time + '\', this)">' +
'data-action="toggle-time-slot">' +
slotData.display +
'<span class="merged-disponibility-count">' + count + '</span>' +
'</div>';
@@ -889,7 +896,7 @@ function updatePlayerPool() {
// Availability indicators are shown per-player, but coaches can still select any player.
if (allRegisteredPlayers.length === 0) {
pool.innerHTML = '<p class="text-muted small">No players registered</p>';
pool.innerHTML = '<p class="text-muted small">{{ _('No players registered') }}</p>';
return;
}
@@ -920,15 +927,15 @@ function updatePlayerPool() {
var availabilityClass = isAvailable ? 'available' : 'unavailable';
html += '<div class="player-item ' + availabilityClass + '" data-player-id="' + pid + '">';
html += '<span class="player-name">' + playerName + '</span>';
html += '<span class="player-name">' + escapeHtml(playerName) + '</span>';
html += '<div class="player-actions">';
html += '<button type="button" class="btn btn-sm btn-primary" onclick="assignToTeam(' + pid + ', 1)">T1</button>';
html += '<button type="button" class="btn btn-sm btn-secondary" onclick="assignToTeam(' + pid + ', 2)">T2</button>';
html += '<button type="button" class="btn btn-sm btn-primary" data-action="assign-team" data-team-side="1">{{ _('T1') }}</button>';
html += '<button type="button" class="btn btn-sm btn-secondary" data-action="assign-team" data-team-side="2">{{ _('T2') }}</button>';
html += '</div>';
html += '</div>';
});
pool.innerHTML = html || '<p class="text-muted small">No players available</p>';
pool.innerHTML = html || '<p class="text-muted small">{{ _('No players available') }}</p>';
updateRandomizePreview();
}
@@ -942,8 +949,8 @@ function assignToTeam(playerId, teamSide) {
var playerName = playerDataById.player_data[playerId];
if (!playerName) return;
var html = '<div class="player-item" data-player-id="' + playerId + '" onclick="returnToPool(' + playerId + ', event)">';
html += playerName;
var html = '<div class="player-item" data-player-id="' + playerId + '" data-action="return-to-pool">';
html += escapeHtml(playerName);
html += '<span class="remove-btn">↺</span>';
html += '</div>';
@@ -952,8 +959,12 @@ function assignToTeam(playerId, teamSide) {
updatePlayerPool();
}
function returnToPool(playerId, event) {
function returnToPool(element, event) {
// The clicked element already carries data-player-id: the generated
// markup sets it, so the value no longer has to be baked into an
// onclick attribute.
if (event) event.stopPropagation();
var playerId = element.getAttribute('data-player-id');
document.querySelectorAll('[data-player-id="' + playerId + '"]').forEach(function(el) {
el.remove();
});
@@ -1057,8 +1068,8 @@ function randomizeTeams() {
var teamDiv = document.getElementById('team1-selection');
var playerName = playerDataById.player_data[pid];
if (playerName) {
var html = '<div class="player-item" data-player-id="' + pid + '" onclick="returnToPool(' + pid + ', event)">';
html += playerName;
var html = '<div class="player-item" data-player-id="' + pid + '" data-action="return-to-pool">';
html += escapeHtml(playerName);
html += '<span class="remove-btn">↺</span>';
html += '</div>';
teamDiv.insertAdjacentHTML('beforeend', html);
@@ -1069,8 +1080,8 @@ function randomizeTeams() {
var teamDiv = document.getElementById('team2-selection');
var playerName = playerDataById.player_data[pid];
if (playerName) {
var html = '<div class="player-item" data-player-id="' + pid + '" onclick="returnToPool(' + pid + ', event)">';
html += playerName;
var html = '<div class="player-item" data-player-id="' + pid + '" data-action="return-to-pool">';
html += escapeHtml(playerName);
html += '<span class="remove-btn">↺</span>';
html += '</div>';
teamDiv.insertAdjacentHTML('beforeend', html);
@@ -1110,5 +1121,32 @@ function togglePresence(matchId, participantId, badgeEl) {
console.error('Error toggling presence:', error);
});
}
// Behaviours declared in the markup, dispatched by the delegated listener
// in main.js. Inline onclick attributes cannot be authorised by a CSP nonce.
registerActions({
'toggle-match-type': toggleMatchType,
'clear-time-selection': clearTimeSelection,
'toggle-time-slot': function (element) {
toggleTimeSlot(parseInt(element.getAttribute('data-day'), 10),
element.getAttribute('data-time'),
element);
},
'update-randomize-preview': updateRandomizePreview,
'randomize-teams': randomizeTeams,
'return-to-pool': returnToPool,
'assign-team': function (element) {
var item = element.closest('[data-player-id]');
if (item) {
assignToTeam(item.getAttribute('data-player-id'),
element.getAttribute('data-team-side'));
}
},
'toggle-presence': function (element) {
togglePresence(element.getAttribute('data-match-id'),
element.getAttribute('data-participant-id'),
element);
},
});
</script>
{% endblock %}
+30 -23
View File
@@ -1,6 +1,6 @@
{% extends "layouts/base.html" %}
{% block title %}My Team(s) - TryoutPro{% endblock %}
{% block page_title %}My Team(s){% endblock %}
{% block title %}{{ _('My Team(s)') }} - UdeS team manager{% endblock %}
{% block page_title %}{{ _('My Team(s)') }}{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / My Team(s)</span>{% endblock %}
{% block content %}
@@ -15,7 +15,7 @@
<div class="team-staff-bar">
<div class="staff-row">
<div class="staff-group">
<span class="staff-label"><i class="fas fa-chalkboard-teacher"></i> Coaches</span>
<span class="staff-label"><i class="fas fa-chalkboard-teacher"></i> {{ _('Coaches') }}</span>
<div class="staff-items">
{% if item.coaches %}
{% for c in item.coaches %}
@@ -27,7 +27,7 @@
</div>
</div>
<div class="staff-group">
<span class="staff-label"><i class="fas fa-user-tie"></i> Managers</span>
<span class="staff-label"><i class="fas fa-user-tie"></i> {{ _('Managers') }}</span>
<div class="staff-items">
{% if item.managers %}
{% for m in item.managers %}
@@ -42,16 +42,16 @@
</div>
<div class="card-body">
<!-- Team Roster -->
<h4 class="mb-3"><i class="fas fa-users"></i> Team Roster</h4>
<h4 class="mb-3"><i class="fas fa-users"></i> {{ _('Team Roster') }}</h4>
{% set roster = team.get_players_with_status() %}
{% if roster %}
<div class="table-container mb-4">
<table class="table">
<thead>
<tr>
<th>Player</th>
<th>Status</th>
<th>Position</th>
<th>{{ _('Player') }}</th>
<th>{{ _('Status') }}</th>
<th>{{ _('Position') }}</th>
</tr>
</thead>
<tbody>
@@ -76,24 +76,24 @@
</div>
{% else %}
<div class="text-center py-3 text-muted">
<i class="fas fa-users-slash"></i> No players on this team.
<i class="fas fa-users-slash"></i> {{ _('No players on this team.') }}
</div>
{% endif %}
<!-- Team Matches -->
<h4 class="mb-3"><i class="fas fa-futbol"></i> Upcoming Matches</h4>
<h4 class="mb-3"><i class="fas fa-futbol"></i> {{ _('Upcoming Matches') }}</h4>
{% if item.matches %}
<div class="table-container">
<table class="table">
<thead>
<tr>
<th>Match</th>
<th>Opponent</th>
<th>Date</th>
<th>Time</th>
<th>Location</th>
<th>Presence</th>
<th>My Status</th>
<th>{{ _('Match') }}</th>
<th>{{ _('Opponent') }}</th>
<th>{{ _('Date') }}</th>
<th>{{ _('Time') }}</th>
<th>{{ _('Location') }}</th>
<th>{{ _('Presence') }}</th>
<th>{{ _('My Status') }}</th>
</tr>
</thead>
<tbody>
@@ -137,7 +137,7 @@
<button class="btn btn-sm {% if mdata.is_confirmed %}btn-success{% else %}btn-outline{% endif %} presence-toggle-btn"
data-match-id="{{ tm.id }}"
data-participant-id="{{ mdata.participant_id }}"
onclick="togglePresence(this)">
data-action="toggle-presence">
{% if mdata.is_confirmed %}✅ Confirmed{% else %}Confirm{% endif %}
</button>
{% else %}
@@ -151,7 +151,7 @@
</div>
{% else %}
<div class="text-center py-3 text-muted">
<i class="fas fa-calendar-alt"></i> No upcoming matches scheduled.
<i class="fas fa-calendar-alt"></i> {{ _('No upcoming matches scheduled.') }}
</div>
{% endif %}
</div>
@@ -162,14 +162,14 @@
<div class="card-body text-center py-5">
<div class="empty-state">
<i class="fas fa-users fa-3x text-muted mb-3"></i>
<h3>No Teams</h3>
<p class="text-muted">You are not currently assigned to any team.</p>
<h3>{{ _('No Teams') }}</h3>
<p class="text-muted">{{ _('You are not currently assigned to any team.') }}</p>
</div>
</div>
</div>
{% endif %}
<script>
<script nonce="{{ csp_nonce }}">
function togglePresence(btn) {
var matchId = btn.getAttribute('data-match-id');
var participantId = btn.getAttribute('data-participant-id');
@@ -198,6 +198,13 @@ function togglePresence(btn) {
console.error('Error:', error);
});
}
// Behaviours are declared in the markup with data-action / data-change and
// dispatched by the delegated listener in main.js. This replaces inline
// onclick attributes, which no CSP nonce is able to authorise.
registerActions({
'toggle-presence': togglePresence,
});
</script>
<style>
@@ -275,4 +282,4 @@ function togglePresence(btn) {
color: #fcd34d;
}
</style>
{% endblock %}
{% endblock %}
+59 -49
View File
@@ -1,6 +1,6 @@
{% extends "layouts/base.html" %}
{% block title %}Notes - TryoutPro{% endblock %}
{% block page_title %}Notes{% endblock %}
{% block title %}{{ _('Notes') }} - UdeS team manager{% endblock %}
{% block page_title %}{{ _('Notes') }}{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / Notes</span>{% endblock %}
{% block content %}
@@ -9,7 +9,7 @@
{% if org_team %}
<div class="card">
<div class="card-header">
<h3><i class="fas fa-users"></i> Team Notes</h3>
<h3><i class="fas fa-users"></i> {{ _('Team Notes') }}</h3>
<span class="badge badge-esport">{{ org_team.name }}</span>
</div>
<div class="card-body">
@@ -17,9 +17,9 @@
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="form-group">
<label for="team_notes_content">Team Notes Content</label>
<textarea name="content" id="team_notes_content" class="form-textarea" rows="4" placeholder="Enter improvement suggestions and notes for your team...">{{ latest_team_note.content if latest_team_note else '' }}</textarea>
<p class="form-text">These notes will be visible to all players on your team.</p>
<label for="team_notes_content">{{ _('Team Notes Content') }}</label>
<textarea name="content" id="team_notes_content" class="form-textarea" rows="4" placeholder="{{ _('Enter improvement suggestions and notes for your team...') }}">{{ latest_team_note.content if latest_team_note else '' }}</textarea>
<p class="form-text">{{ _('These notes will be visible to all players on your team.') }}</p>
</div>
<div class="form-actions">
@@ -35,7 +35,7 @@
<!-- Add Personal Note Section -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-user-friends"></i> Add Personal Note</h3>
<h3><i class="fas fa-user-friends"></i> {{ _('Add Personal Note') }}</h3>
{% if org_team %}
<span class="badge badge-esport">{{ org_team.name }}</span>
{% endif %}
@@ -45,9 +45,9 @@
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="form-group">
<label for="player_id">Select Player</label>
<label for="player_id">{{ _('Select Player') }}</label>
<select name="player_id" id="player_id" class="form-select" required>
<option value="">-- Select a Player --</option>
<option value="">{{ _('-- Select a Player --') }}</option>
{% for player in players %}
<option value="{{ player.id }}">{{ player.username }}</option>
{% endfor %}
@@ -55,38 +55,38 @@
</div>
<div class="form-group">
<label for="note_content">Note Content</label>
<textarea name="content" id="note_content" class="form-textarea" rows="3" placeholder="Enter personal feedback or coaching tips for this player..." required></textarea>
<p class="form-text">These notes will only be visible to the selected player.</p>
<label for="note_content">{{ _('Note Content') }}</label>
<textarea name="content" id="note_content" class="form-textarea" rows="3" placeholder="{{ _('Enter personal feedback or coaching tips for this player...') }}" required></textarea>
<p class="form-text">{{ _('These notes will only be visible to the selected player.') }}</p>
</div>
<div class="form-group">
<label for="context">Context (Optional)</label>
<p class="form-text text-muted">Link this note to a specific match, tryout, or team for better organization.</p>
<label for="context">{{ _('Context (Optional)') }}</label>
<p class="form-text text-muted">{{ _('Link this note to a specific match, tryout, or team for better organization.') }}</p>
<div class="form-row">
<div class="form-group">
<label for="note_match_id">Match</label>
<label for="note_match_id">{{ _('Match') }}</label>
<select name="match_id" id="note_match_id" class="form-select">
<option value="">-- Select Match --</option>
<option value="">{{ _('-- Select Match --') }}</option>
{% for match in matches %}
<option value="{{ match.id }}">{{ match.title }} - {{ match.date.strftime('%m/%d/%Y') }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label for="note_tryout_id">Tryout</label>
<label for="note_tryout_id">{{ _('Tryout') }}</label>
<select name="tryout_id" id="note_tryout_id" class="form-select">
<option value="">-- Select Tryout --</option>
<option value="">{{ _('-- Select Tryout --') }}</option>
{% for tryout in tryouts %}
<option value="{{ tryout.id }}">{{ tryout.title }} - {{ tryout.date.strftime('%m/%d/%Y') }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label for="note_team_id">Team</label>
<label for="note_team_id">{{ _('Team') }}</label>
<select name="team_id" id="note_team_id" class="form-select">
<option value="">-- Select Team --</option>
<option value="">{{ _('-- Select Team --') }}</option>
{% for team in teams %}
<option value="{{ team.id }}">{{ team.name }}</option>
{% endfor %}
@@ -97,7 +97,7 @@
<div class="form-actions">
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> Add Note
<i class="fas fa-save"></i> {{ _('Add Note') }}
</button>
</div>
</form>
@@ -108,7 +108,7 @@
<!-- One on One Requests Section -->
<div class="card mt-4">
<div class="card-header">
<h3><i class="fas fa-calendar-check"></i> One on One Requests</h3>
<h3><i class="fas fa-calendar-check"></i> {{ _('One on One Requests') }}</h3>
{% if org_team %}
<span class="badge badge-esport">{{ org_team.name }}</span>
{% endif %}
@@ -119,12 +119,12 @@
<table class="table">
<thead>
<tr>
<th>Player</th>
<th>Date</th>
<th>Time</th>
<th>Discussion Points</th>
<th>Status</th>
<th>Actions</th>
<th>{{ _('Player') }}</th>
<th>{{ _('Date') }}</th>
<th>{{ _('Time') }}</th>
<th>{{ _('Discussion Points') }}</th>
<th>{{ _('Status') }}</th>
<th>{{ _('Actions') }}</th>
</tr>
</thead>
<tbody>
@@ -147,12 +147,12 @@
{% if req.status == 'pending' %}
<form method="POST" action="{{ url_for('users.accept_one_on_one', request_id=req.id) }}" style="display:inline;">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<button type="submit" class="btn btn-sm btn-success" title="Accept">
<i class="fas fa-check"></i> Accept
<button type="submit" class="btn btn-sm btn-success" title="{{ _('Accept') }}">
<i class="fas fa-check"></i> {{ _('Accept') }}
</button>
</form>
<button type="button" class="btn btn-sm btn-danger" onclick="showRejectModal({{ req.id }})" title="Refuse">
<i class="fas fa-times"></i> Refuse
<button type="button" class="btn btn-sm btn-danger" data-action="show-reject-modal" data-request-id="{{ req.id }}" title="{{ _('Refuse') }}">
<i class="fas fa-times"></i> {{ _('Refuse') }}
</button>
{% elif req.status == 'rejected' and req.coach_rejection_message %}
<span class="text-muted small" title="{{ req.coach_rejection_message }}">Reason: {{ req.coach_rejection_message[:50] }}{% if req.coach_rejection_message|length > 50 %}...{% endif %}</span>
@@ -164,30 +164,30 @@
</table>
</div>
{% else %}
<p class="text-muted">No One on One requests from your players yet.</p>
<p class="text-muted">{{ _('No One on One requests from your players yet.') }}</p>
{% endif %}
</div>
</div>
<!-- Reject Modal -->
<div id="rejectModal" class="modal" style="display:none;">
<div class="modal-overlay" onclick="hideRejectModal()"></div>
<div class="modal-overlay" data-action="hide-reject-modal"></div>
<div class="modal-content">
<div class="modal-header">
<h4><i class="fas fa-times-circle"></i> Reject One on One Request</h4>
<button type="button" class="modal-close" onclick="hideRejectModal()">&times;</button>
<h4><i class="fas fa-times-circle"></i> {{ _('Reject One on One Request') }}</h4>
<button type="button" class="modal-close" data-action="hide-reject-modal">&times;</button>
</div>
<form id="rejectForm" method="POST" action="">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="modal-body">
<div class="form-group">
<label for="rejection_reason">Reason for rejection (optional):</label>
<textarea name="rejection_reason" id="rejection_reason" class="form-textarea" rows="3" placeholder="Let the player know why this time doesn't work..."></textarea>
<label for="rejection_reason">{{ _('Reason for rejection (optional):') }}</label>
<textarea name="rejection_reason" id="rejection_reason" class="form-textarea" rows="3" placeholder="{{ _('Let the player know why this time doesn\'t work...') }}"></textarea>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="hideRejectModal()">Cancel</button>
<button type="submit" class="btn btn-danger">Reject Request</button>
<button type="button" class="btn btn-secondary" data-action="hide-reject-modal">{{ _('Cancel') }}</button>
<button type="submit" class="btn btn-danger">{{ _('Reject Request') }}</button>
</div>
</form>
</div>
@@ -198,14 +198,14 @@
{% if org_team and team_notes %}
<div class="card">
<div class="card-header">
<h3><i class="fas fa-history"></i> Team Notes History</h3>
<h3><i class="fas fa-history"></i> {{ _('Team Notes History') }}</h3>
</div>
<div class="card-body">
<table class="table">
<thead>
<tr>
<th>Last Updated</th>
<th>Content Preview</th>
<th>{{ _('Last Updated') }}</th>
<th>{{ _('Content Preview') }}</th>
</tr>
</thead>
<tbody>
@@ -225,7 +225,7 @@
{% if personal_notes %}
<div class="card">
<div class="card-header">
<h3><i class="fas fa-sticky-note"></i> Recent Personal Notes</h3>
<h3><i class="fas fa-sticky-note"></i> {{ _('Recent Personal Notes') }}</h3>
</div>
<div class="card-body">
<div class="detail-grid">
@@ -240,13 +240,13 @@
{% if note.match_id or note.team_id or note.tryout_id %}
<div class="mt-2">
{% if note.match_id and note.match %}
<span class="badge badge-info" title="From match"><i class="fas fa-futbol"></i> {{ note.match.title }}</span>
<span class="badge badge-info" title="{{ _('From match') }}"><i class="fas fa-futbol"></i> {{ note.match.title }}</span>
{% endif %}
{% if note.team_id and note.team %}
<span class="badge badge-warning" title="From team"><i class="fas fa-users"></i> {{ note.team.name }}</span>
<span class="badge badge-warning" title="{{ _('From team') }}"><i class="fas fa-users"></i> {{ note.team.name }}</span>
{% endif %}
{% if note.tryout_id and note.tryout %}
<span class="badge badge-success" title="From tryout"><i class="fas fa-calendar-alt"></i> {{ note.tryout.title }}</span>
<span class="badge badge-success" title="{{ _('From tryout') }}"><i class="fas fa-calendar-alt"></i> {{ note.tryout.title }}</span>
{% endif %}
</div>
{% endif %}
@@ -260,7 +260,7 @@
{% endblock %}
{% block scripts %}
<script>
<script nonce="{{ csp_nonce }}">
function showRejectModal(requestId) {
const modal = document.getElementById('rejectModal');
const form = document.getElementById('rejectForm');
@@ -272,5 +272,15 @@ function hideRejectModal() {
document.getElementById('rejectModal').style.display = 'none';
document.getElementById('rejection_reason').value = '';
}
// Behaviours are declared in the markup with data-action / data-change and
// dispatched by the delegated listener in main.js. This replaces inline
// onclick attributes, which no CSP nonce is able to authorise.
registerActions({
'show-reject-modal': function (element) {
showRejectModal(element.getAttribute('data-request-id'));
},
'hide-reject-modal': hideRejectModal,
});
</script>
{% endblock %}
{% endblock %}
+179 -194
View File
@@ -1,13 +1,65 @@
{% extends "layouts/base.html" %}
{% block title %}One on One - TryoutPro{% endblock %}
{% block page_title %}One on One{% endblock %}
{% block title %}{{ _('One on One') }} - UdeS team manager{% endblock %}
{% block page_title %}{{ _('One on One') }}{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / One on One</span>{% endblock %}
{% block content %}
<div class="dashboard-grid">
<!-- Team Notes Section -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-users"></i> {{ _('Team Notes') }}</h3>
{% if org_team %}
<span class="badge badge-esport">{{ org_team.name }}</span>
{% endif %}
</div>
<div class="card-body">
{% if team_notes %}
{% for note in team_notes %}
<div class="detail-grid mt-4">
<div class="detail-item full-width">
<span class="detail-label"><i class="fas fa-user"></i> Coach: {{ note.coach.username if note.coach else 'Unknown Coach' }}</span>
<span class="detail-value">{{ note.content | nl2br }}</span>
</div>
</div>
<p class="text-muted small mt-2">Updated: {{ note.updated_at.strftime('%B %d, %Y at %I:%M %p') }}</p>
{% endfor %}
{% else %}
<p class="text-muted">{{ _('No team notes have been added yet. Your coach will post improvement suggestions here.') }}</p>
{% endif %}
</div>
</div>
<!-- Personal Notes Section -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-user"></i> {{ _('Personal Notes') }}</h3>
{% if coach %}
<span class="badge badge-coach">From: {{ coach.username }}</span>
{% endif %}
</div>
<div class="card-body">
{% if personal_notes %}
{% for note in personal_notes %}
<div class="detail-grid mt-4">
<div class="detail-item full-width">
<span class="detail-label"><i class="fas fa-sticky-note"></i> Note from {{ note.coach.username if note.coach else 'Unknown Coach' }}</span>
<span class="detail-value">{{ note.content | nl2br }}</span>
</div>
</div>
<p class="text-muted small mt-2">Added: {{ note.created_at.strftime('%B %d, %Y at %I:%M %p') }}</p>
{% endfor %}
{% else %}
<p class="text-muted">{{ _('No personal notes have been added yet. Your coach may provide individual feedback here.') }}</p>
{% endif %}
</div>
</div>
</div>
<!-- My One on One Requests Tracker -->
<div class="card mb-4">
<div class="card mt-4">
<div class="card-header">
<h3><i class="fas fa-list-check"></i> My One on One Requests</h3>
<h3><i class="fas fa-list-check"></i> {{ _('My One on One Requests') }}</h3>
</div>
<div class="card-body">
{% if my_requests %}
@@ -15,30 +67,26 @@
<table class="table">
<thead>
<tr>
<th>Coach</th>
<th>Team</th>
<th>Date</th>
<th>Time</th>
<th>Discussion Points</th>
<th>Status</th>
<th>Coach Response</th>
<th>{{ _('Date') }}</th>
<th>{{ _('Time') }}</th>
<th>{{ _('Discussion Points') }}</th>
<th>{{ _('Status') }}</th>
<th>{{ _('Coach Response') }}</th>
</tr>
</thead>
<tbody>
{% for req in my_requests %}
<tr>
<td>{{ req.coach.full_name if req.coach else 'Unknown Coach' }}</td>
<td>{{ req.team.name if req.team else '—' }}</td>
<td>{{ req.date.strftime('%b %d, %Y') }}</td>
<td>{{ req.start_time.strftime('%I:%M %p') }} - {{ req.end_time.strftime('%I:%M %p') }}</td>
<td>{{ req.points or 'N/A' }}</td>
<td>
{% if req.status == 'pending' %}
<span class="badge badge-warning"><i class="fas fa-clock"></i> Pending</span>
<span class="badge badge-warning"><i class="fas fa-clock"></i> {{ _('Pending') }}</span>
{% elif req.status == 'approved' %}
<span class="badge badge-success"><i class="fas fa-check-circle"></i> Approved</span>
<span class="badge badge-success"><i class="fas fa-check-circle"></i> {{ _('Approved') }}</span>
{% elif req.status == 'rejected' %}
<span class="badge badge-danger"><i class="fas fa-times-circle"></i> Rejected</span>
<span class="badge badge-danger"><i class="fas fa-times-circle"></i> {{ _('Rejected') }}</span>
{% endif %}
</td>
<td>
@@ -60,83 +108,76 @@
</table>
</div>
{% else %}
<p class="text-muted">You haven't made any One on One requests yet.</p>
<p class="text-muted">{{ _('You haven\'t made any One on One requests yet.') }}</p>
{% endif %}
</div>
</div>
<!-- One on One Request Section -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-calendar-check"></i> Request One on One Session</h3>
</div>
<div class="card-body">
{% if coach_options %}
<form method="POST" action="{{ url_for('users.one_on_one') }}" class="form" id="oneOnOneForm">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="form-row">
<div class="form-group col-6">
<label for="coach_id">Select Coach</label>
<select name="coach_id" id="coach_id" class="form-select" onchange="onCoachChange()" required>
<option value="">-- Select Coach --</option>
{% for c in coach_options %}
<option value="{{ c.coach_id }}">{{ c.coach_name }}</option>
{% endfor %}
</select>
<div class="dashboard-grid mt-4">
<!-- One on One Request Section -->
<div class="card" style="grid-column: 1 / -1;">
<div class="card-header">
<h3><i class="fas fa-calendar-check"></i> {{ _('Request One on One Session') }}</h3>
{% if coach %}
<span class="badge badge-info">Coach: {{ coach.username }}</span>
{% endif %}
</div>
<div class="card-body">
{% if coach %}
<form method="POST" action="{{ url_for('users.one_on_one') }}" class="form" id="oneOnOneForm">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="form-row">
<div class="form-group">
<label for="date">{{ _('Select Date') }}</label>
<select name="date" id="date" class="form-select" data-change="update-time-slots" required>
{% for d in dates %}
<option value="{{ d.value }}" data-day="{{ d.day_of_week }}">{{ d.display }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label for="start_time">{{ _('Start Time') }}</label>
<select name="start_time" id="start_time" class="form-select" data-change="update-end-times" required>
<option value="">{{ _('-- Select Date First --') }}</option>
</select>
</div>
<div class="form-group">
<label for="end_time">{{ _('End Time') }}</label>
<select name="end_time" id="end_time" class="form-select" required>
<option value="">{{ _('-- Select Start Time First --') }}</option>
</select>
</div>
</div>
<div class="form-group col-6">
<label for="org_team_id">Select Team <span class="text-muted">(optional)</span></label>
<select name="org_team_id" id="org_team_id" class="form-select">
<option value="">-- Any Team --</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="date">Select Date</label>
<select name="date" id="date" class="form-select" onchange="updateTimeSlots()" required>
<option value="">-- Select Coach First --</option>
</select>
<label for="points">Discussion Points <span class="text-muted">(What would you like to discuss?)</span></label>
<textarea name="points" id="points" class="form-textarea" placeholder="{{ _('Enter topics you\'d like to cover in your One on One session...') }}" rows="4"></textarea>
</div>
<div class="form-group">
<label for="start_time">Start Time</label>
<select name="start_time" id="start_time" class="form-select" onchange="updateEndTimeOptions()" required>
<option value="">-- Select Date First --</option>
</select>
</div>
<div class="form-group">
<label for="end_time">End Time</label>
<select name="end_time" id="end_time" class="form-select" required>
<option value="">-- Select Start Time First --</option>
</select>
</div>
</div>
<div class="form-group">
<label for="points">Discussion Points <span class="text-muted">(What would you like to discuss?)</span></label>
<textarea name="points" id="points" class="form-textarea" placeholder="Enter topics you'd like to cover in your One on One session..." rows="4"></textarea>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">
<i class="fas fa-paper-plane"></i> Send Request
</button>
</div>
</form>
{% else %}
<p class="text-muted">You need to be assigned to a team with a coach to request a One on One session.</p>
{% endif %}
<div class="form-actions">
<button type="submit" class="btn btn-primary">
<i class="fas fa-paper-plane"></i> {{ _('Send Request') }}
</button>
</div>
</form>
{% else %}
<p class="text-muted">{{ _('You need to be assigned to a team with a coach to request a One on One session.') }}</p>
{% endif %}
</div>
</div>
</div>
{% if coach %}
<!-- Hidden data for JavaScript -->
<script id="coach-availability-data" type="application/json" nonce="{{ csp_nonce }}">
{{ coach_availability | tojson }}
</script>
{% endif %}
{% endblock %}
{% block scripts %}
<script>
// Coach options injected server-side
const COACH_OPTIONS = {{ coach_options | tojson }};
<script nonce="{{ csp_nonce }}">
// Time slots from 8:00 AM to 10:00 PM
const TIME_SLOTS = [];
for (let h = 8; h <= 22; h++) {
@@ -149,122 +190,62 @@ for (let h = 8; h <= 22; h++) {
}
}
const DAY_NAMES = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
// Coach availability data
let coachAvailability = [];
// Helper to convert "HH:MM" to minutes since midnight
function toMinutes(timeStr) {
const parts = timeStr.split(':');
return parseInt(parts[0]) * 60 + parseInt(parts[1]);
}
// Initialize
document.addEventListener('DOMContentLoaded', function() {
loadCoachAvailability();
updateTimeSlots();
});
// Build date dropdown for the next 14 days filtered to days the coach has availability
function buildDateOptions() {
const dateSelect = document.getElementById('date');
dateSelect.innerHTML = '<option value="">-- Select Date --</option>';
const availableDays = new Set(coachAvailability.map(av => av.day_of_week));
if (availableDays.size === 0) {
return;
function loadCoachAvailability() {
const dataEl = document.getElementById('coach-availability-data');
if (!dataEl) return;
try {
coachAvailability = JSON.parse(dataEl.textContent);
} catch (e) {
coachAvailability = [];
}
const today = new Date();
for (let i = 0; i < 14; i++) {
const d = new Date(today);
d.setDate(today.getDate() + i);
if (availableDays.has(d.getDay())) {
const value = d.toISOString().split('T')[0];
const display = d.toLocaleDateString('en-US', {
weekday: 'long', year: 'numeric', month: 'long', day: 'numeric'
});
const option = document.createElement('option');
option.value = value;
option.setAttribute('data-day', d.getDay());
option.textContent = display;
dateSelect.appendChild(option);
}
}
// Reset dependent dropdowns
document.getElementById('start_time').innerHTML = '<option value="">-- Select Date First --</option>';
document.getElementById('end_time').innerHTML = '<option value="">-- Select Start Time First --</option>';
}
// Populate team dropdown for the selected coach
function updateTeamOptions(coachId) {
const teamSelect = document.getElementById('org_team_id');
teamSelect.innerHTML = '<option value="">-- Any Team --</option>';
const coach = COACH_OPTIONS.find(c => c.coach_id === coachId);
if (!coach) return;
coach.teams.forEach(team => {
const option = document.createElement('option');
option.value = team.team_id;
option.textContent = team.team_name;
teamSelect.appendChild(option);
});
}
// Fetch availability for the selected coach
function fetchCoachAvailability(coachId) {
coachAvailability = [];
if (!coachId) {
buildDateOptions();
return;
}
fetch(`/users/one-on-one/coaches/${coachId}/availability`)
.then(response => response.json())
.then(data => {
coachAvailability = data.availability || [];
buildDateOptions();
})
.catch(error => {
console.error('Error loading coach availability:', error);
coachAvailability = [];
buildDateOptions();
});
}
function onCoachChange() {
const coachId = parseInt(document.getElementById('coach_id').value);
updateTeamOptions(coachId);
fetchCoachAvailability(coachId);
}
function updateTimeSlots() {
const dateSelect = document.getElementById('date');
const startTimeSelect = document.getElementById('start_time');
const selectedOption = dateSelect.options[dateSelect.selectedIndex];
if (!selectedOption || !selectedOption.value) {
startTimeSelect.innerHTML = '<option value="">-- Select Date First --</option>';
document.getElementById('end_time').innerHTML = '<option value="">-- Select Start Time First --</option>';
return;
}
const dayOfWeek = parseInt(selectedOption.getAttribute('data-day'));
// Get available time slots for this day
const dayAvailability = coachAvailability.filter(av => av.day_of_week === dayOfWeek);
// Build available slots - collect all available minutes then sort
const availableSlots = [];
const availableMinutes = [];
dayAvailability.forEach(av => {
const startMinutes = toMinutes(av.start_time);
const endMinutes = toMinutes(av.end_time);
const startMinutes = av.start_time.split(':').reduce((acc, val, i) => acc + parseInt(val) * (i === 0 ? 60 : 1), 0);
const endMinutes = av.end_time.split(':').reduce((acc, val, i) => acc + parseInt(val) * (i === 0 ? 60 : 1), 0);
// Add 30-minute slots
for (let m = startMinutes; m < endMinutes; m += 30) {
availableMinutes.push(m);
}
});
// Sort minutes and convert to time strings
availableMinutes.sort((a, b) => a - b);
startTimeSelect.innerHTML = '<option value="">-- Select Start Time --</option>';
availableMinutes.forEach(m => {
const hour = Math.floor(m / 60);
const minute = m % 60;
const timeStr = (hour < 10 ? '0' : '') + hour + ':' + (minute < 10 ? '0' : '') + minute;
const slotData = TIME_SLOTS.find(s => s.time === timeStr);
availableSlots.push(timeStr);
});
// Update start time options (sorted)
startTimeSelect.innerHTML = '<option value="">{{ _('-- Select Start Time --') }}</option>';
availableSlots.forEach(slot => {
const slotData = TIME_SLOTS.find(s => s.time === slot);
if (slotData) {
const option = document.createElement('option');
option.value = slotData.time;
@@ -272,7 +253,8 @@ function updateTimeSlots() {
startTimeSelect.appendChild(option);
}
});
// Reset end time options
updateEndTimeOptions();
}
@@ -280,35 +262,36 @@ function updateEndTimeOptions() {
const dateSelect = document.getElementById('date');
const startTimeSelect = document.getElementById('start_time');
const endTimeSelect = document.getElementById('end_time');
const selectedOption = dateSelect.options[dateSelect.selectedIndex];
if (!selectedOption || !selectedOption.value) {
endTimeSelect.innerHTML = '<option value="">-- Select Start Time First --</option>';
return;
}
const dayOfWeek = parseInt(selectedOption.getAttribute('data-day'));
const selectedStart = startTimeSelect.value;
if (!selectedStart) {
endTimeSelect.innerHTML = '<option value="">-- Select Start Time First --</option>';
endTimeSelect.innerHTML = '<option value="">{{ _('-- Select Start Time First --') }}</option>';
return;
}
// Get available minutes for this day
const dayAvailability = coachAvailability.filter(av => av.day_of_week === dayOfWeek);
const availableMinutes = [];
dayAvailability.forEach(av => {
const startMinutes = toMinutes(av.start_time);
const endMinutes = toMinutes(av.end_time);
const startMinutes = av.start_time.split(':').reduce((acc, val, i) => acc + parseInt(val) * (i === 0 ? 60 : 1), 0);
const endMinutes = av.end_time.split(':').reduce((acc, val, i) => acc + parseInt(val) * (i === 0 ? 60 : 1), 0);
for (let m = startMinutes; m < endMinutes; m += 30) {
availableMinutes.push(m);
}
});
const startMinutesVal = toMinutes(selectedStart);
const validEndTimes = availableMinutes.filter(m => m > startMinutesVal).sort((a, b) => a - b);
endTimeSelect.innerHTML = '<option value="">-- Select End Time --</option>';
// Convert selected start to minutes
const startMinutesVal = selectedStart.split(':').reduce((acc, val, i) => acc + parseInt(val) * (i === 0 ? 60 : 1), 0);
// Filter end times that are after start time
const validEndTimes = availableMinutes.filter(m => m > startMinutesVal);
validEndTimes.sort((a, b) => a - b);
// Update end time options
endTimeSelect.innerHTML = '<option value="">{{ _('-- Select End Time --') }}</option>';
validEndTimes.forEach(m => {
const hour = Math.floor(m / 60);
const minute = m % 60;
@@ -323,10 +306,12 @@ function updateEndTimeOptions() {
});
}
document.addEventListener('DOMContentLoaded', function() {
if (COACH_OPTIONS.length > 0) {
onCoachChange();
}
// Behaviours are declared in the markup with data-action / data-change and
// dispatched by the delegated listener in main.js. This replaces inline
// onclick attributes, which no CSP nonce is able to authorise.
registerActions({
'update-time-slots': updateTimeSlots,
'update-end-times': updateEndTimeOptions,
});
</script>
{% endblock %}
{% endblock %}
+11 -11
View File
@@ -1,12 +1,12 @@
{% extends "layouts/base.html" %}
{% block title %}Personal Notes - TryoutPro{% endblock %}
{% block page_title %}Personal Notes{% endblock %}
{% block title %}{{ _('Personal Notes') }} - UdeS team manager{% endblock %}
{% block page_title %}{{ _('Personal Notes') }}{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / Personal Notes</span>{% endblock %}
{% block content %}
<div class="card">
<div class="card-header">
<h3><i class="fas fa-user-friends"></i> Add Personal Note</h3>
<h3><i class="fas fa-user-friends"></i> {{ _('Add Personal Note') }}</h3>
{% if org_team %}
<span class="badge badge-esport">{{ org_team.name }}</span>
{% endif %}
@@ -16,9 +16,9 @@
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="form-group">
<label for="player_id">Select Player</label>
<label for="player_id">{{ _('Select Player') }}</label>
<select name="player_id" id="player_id" class="form-select" required>
<option value="">-- Select a Player --</option>
<option value="">{{ _('-- Select a Player --') }}</option>
{% for player in players %}
<option value="{{ player.id }}">{{ player.username }}</option>
{% endfor %}
@@ -26,14 +26,14 @@
</div>
<div class="form-group">
<label for="content">Note Content</label>
<textarea name="content" id="content" class="form-textarea" rows="4" placeholder="Enter personal feedback or coaching tips for this player..." required></textarea>
<p class="form-text">These notes will only be visible to the selected player.</p>
<label for="content">{{ _('Note Content') }}</label>
<textarea name="content" id="content" class="form-textarea" rows="4" placeholder="{{ _('Enter personal feedback or coaching tips for this player...') }}" required></textarea>
<p class="form-text">{{ _('These notes will only be visible to the selected player.') }}</p>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">
<i class="fas fa-save"></i> Add Note
<i class="fas fa-save"></i> {{ _('Add Note') }}
</button>
</div>
</form>
@@ -43,7 +43,7 @@
{% if personal_notes %}
<div class="card mt-4">
<div class="card-header">
<h3><i class="fas fa-sticky-note"></i> Recent Notes</h3>
<h3><i class="fas fa-sticky-note"></i> {{ _('Recent Notes') }}</h3>
</div>
<div class="card-body">
<div class="detail-grid">
@@ -61,4 +61,4 @@
</div>
</div>
{% endif %}
{% endblock %}
{% endblock %}
+11 -11
View File
@@ -1,6 +1,6 @@
{% extends "layouts/base.html" %}
{% block title %}My Notes - TryoutPro{% endblock %}
{% block page_title %}My Notes{% endblock %}
{% block title %}{{ _('My Notes') }} - UdeS team manager{% endblock %}
{% block page_title %}{{ _('My Notes') }}{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / One on One / My Notes</span>{% endblock %}
{% block content %}
@@ -8,7 +8,7 @@
<!-- Personal Notes Section -->
<div class="card" style="grid-column: 1 / -1;">
<div class="card-header">
<h3><i class="fas fa-user"></i> Personal Notes</h3>
<h3><i class="fas fa-user"></i> {{ _('Personal Notes') }}</h3>
{% if org_team %}
<span class="badge badge-coach">Team: {{ org_team.name }}</span>
{% endif %}
@@ -24,17 +24,17 @@
<span class="detail-value">{{ note.content | nl2br }}</span>
<div class="mt-2">
{% if note.match_id and note.match %}
<span class="badge badge-info" title="From match">
<span class="badge badge-info" title="{{ _('From match') }}">
<i class="fas fa-futbol"></i> Match: {{ note.match.title }}
</span>
{% endif %}
{% if note.team_id and note.team %}
<span class="badge badge-warning" title="From team">
<span class="badge badge-warning" title="{{ _('From team') }}">
<i class="fas fa-users"></i> Team: {{ note.team.name }}
</span>
{% endif %}
{% if note.tryout_id and note.tryout %}
<span class="badge badge-success" title="From tryout">
<span class="badge badge-success" title="{{ _('From tryout') }}">
<i class="fas fa-calendar-alt"></i> Tryout: {{ note.tryout.title }}
</span>
{% endif %}
@@ -44,7 +44,7 @@
<p class="text-muted small mt-2">Added: {{ note.created_at.strftime('%B %d, %Y at %I:%M %p') }}</p>
{% endfor %}
{% else %}
<p class="text-muted">No personal notes have been added yet. Your coach may provide individual feedback here.</p>
<p class="text-muted">{{ _('No personal notes have been added yet. Your coach may provide individual feedback here.') }}</p>
{% endif %}
</div>
</div>
@@ -52,7 +52,7 @@
<!-- Team Notes Section -->
<div class="card" style="grid-column: 1 / -1;">
<div class="card-header">
<h3><i class="fas fa-users"></i> Team Notes</h3>
<h3><i class="fas fa-users"></i> {{ _('Team Notes') }}</h3>
{% if org_team %}
<span class="badge badge-esport">{{ org_team.name }}</span>
{% endif %}
@@ -69,7 +69,7 @@
<p class="text-muted small mt-2">Updated: {{ note.updated_at.strftime('%B %d, %Y at %I:%M %p') }}</p>
{% endfor %}
{% else %}
<p class="text-muted">No team notes have been added yet. Your coach will post improvement suggestions here.</p>
<p class="text-muted">{{ _('No team notes have been added yet. Your coach will post improvement suggestions here.') }}</p>
{% endif %}
</div>
</div>
@@ -78,8 +78,8 @@
<div class="card mt-4">
<div class="card-body text-center">
<a href="{{ url_for('users.one_on_one') }}" class="btn btn-primary">
<i class="fas fa-calendar-check"></i> Request One on One Session
<i class="fas fa-calendar-check"></i> {{ _('Request One on One Session') }}
</a>
</div>
</div>
{% endblock %}
{% endblock %}
+77 -47
View File
@@ -1,59 +1,89 @@
{% extends "layouts/base.html" %}
{% block title %}Players to Evaluate - TryoutPro{% endblock %}
{% block page_title %}Players to Evaluate{% endblock %}
{% block title %}{{ _('Players to Evaluate') }} - UdeS team manager{% 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>
<h3><i class="fas fa-users"></i> Select players to evaluate 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">
<p class="text-muted mb-3">Choose the players you want to evaluate, then load all of them on a single page.</p>
<form method="GET" action="{{ url_for('evaluations.batch_evaluate', tryout_id=tryout.id) }}">
<div class="table-container">
<table class="table">
<thead>
<tr>
<th><input type="checkbox" id="select-all" data-action="toggle-all"></th>
<th>Player</th>
<th>Contact</th>
<th>Attendance</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for entry in players %}
<tr>
<td>
<input type="checkbox" name="player_ids" value="{{ entry.player.id }}" class="player-check">
</td>
<td>
<div class="user-mini">
<div class="avatar-sm">{{ entry.player.username[:2] | upper }}</div>
<span>{{ entry.player.username }}</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>
</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-outline">
<i class="fas fa-clipboard"></i> {% if entry.evaluated %}View/Edit{% else %}Single{% endif %}
</a>
</td>
</tr>
{% else %}
<tr>
<td colspan="6" class="text-center">No players registered for this tryout.</td>
</tr>
{% endfor %}
</tbody>
</table>
</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-clipboard-check"></i> Evaluate Selected
</button>
</div>
</form>
</div>
</div>
{% endblock %}
{% endblock %}
{% block scripts %}
<script nonce="{{ csp_nonce }}">
function toggleAll() {
var boxes = document.querySelectorAll('.player-check');
var master = document.getElementById('select-all');
for (var i = 0; i < boxes.length; i++) {
boxes[i].checked = master.checked;
}
}
registerActions({
'toggle-all': toggleAll,
});
</script>
{% endblock %}
+49 -38
View File
@@ -1,15 +1,15 @@
{% extends "layouts/base.html" %}
{% block title %}My Profile - TryoutPro{% endblock %}
{% block page_title %}My Profile{% endblock %}
{% block title %}{{ _('My Profile') }} - UdeS team manager{% 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.list_contracts') }}" class="btn btn-sm btn-info">
<i class="fas fa-file-contract"></i> Contracts
<i class="fas fa-file-contract"></i> {{ _('Contracts') }}
</a>
<a href="{{ url_for('users.edit_profile') }}" class="btn btn-sm btn-primary">
<i class="fas fa-edit"></i> Edit Profile
<i class="fas fa-edit"></i> {{ _('Edit Profile') }}
</a>
</div>
{% endblock %}
@@ -18,7 +18,7 @@
<div class="dashboard-grid">
<div class="card">
<div class="card-header">
<h3><i class="fas fa-user-circle"></i> Account Information</h3>
<h3><i class="fas fa-user-circle"></i> {{ _('Account Information') }}</h3>
</div>
<div class="card-body">
<div class="profile-header">
@@ -60,12 +60,12 @@
<!-- E-Sports Profile Card -->
<div class="card">
<div class="card-header">
<h3><i class="fas fa-gamepad"></i> E-Sports Profile</h3>
<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-gamepad"></i> Gamertags</span>
<span class="detail-label"><i class="fas fa-gamepad"></i> {{ _('Gamertags') }}</span>
<span class="detail-value">
{% set games_list = user.get_games_list() %}
{% if games_list %}
@@ -91,7 +91,7 @@
</span>
</div>
<div class="detail-item">
<span class="detail-label"><i class="fab fa-discord"></i> Discord</span>
<span class="detail-label"><i class="fab fa-discord"></i> {{ _('Discord') }}</span>
<span class="detail-value">
{% if user.discord_username %}
{{ user.discord_username }}
@@ -101,7 +101,7 @@
</span>
</div>
<div class="detail-item">
<span class="detail-label"><i class="fas fa-link"></i> League OS</span>
<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">
@@ -118,29 +118,29 @@
<div class="card">
<div class="card-header">
<h3><i class="fas fa-chart-bar"></i> My Statistics</h3>
<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>
<p>{{ _('Tryouts Registered') }}</p>
</div>
<div class="stat-item">
<h4>{{ user.team_assignments.count() }}</h4>
<p>Team Assignments</p>
<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>
<p>{{ _('Evaluations Given') }}</p>
</div>
</div>
{% else %}
<p class="text-muted">No statistics available for this role.</p>
<p class="text-muted">{{ _('No statistics available for this role.') }}</p>
{% endif %}
</div>
</div>
@@ -192,7 +192,7 @@
{% if user.role == 'player' %}
<div class="card">
<div class="card-header">
<h3><i class="fas fa-file-contract"></i> My Contracts</h3>
<h3><i class="fas fa-file-contract"></i> {{ _('My Contracts') }}</h3>
</div>
<div class="card-body">
{% if contracts %}
@@ -214,11 +214,11 @@
</div>
<div class="mt-3">
<a href="{{ url_for('users.list_contracts') }}" class="btn btn-sm btn-primary">
<i class="fas fa-folder-open"></i> View All Contracts
<i class="fas fa-folder-open"></i> {{ _('View All Contracts') }}
</a>
</div>
{% else %}
<p class="text-muted">No contracts have been uploaded for you yet.</p>
<p class="text-muted">{{ _('No contracts have been uploaded for you yet.') }}</p>
{% endif %}
</div>
</div>
@@ -228,19 +228,16 @@
{% if user.role == 'player' %}
<div class="card" style="grid-column: 1 / -1;">
<div class="card-header">
<h3><i class="fas fa-clock"></i> My Disponibilities</h3>
<h3><i class="fas fa-clock"></i> {{ _('My Disponibilities') }}</h3>
</div>
<div class="card-body">
<p class="text-muted small">Select your available time blocks for matches (5pm to 12am). Green = selected, Gray = available to select.</p>
<p class="text-muted small">{{ _('Select your available time blocks for matches (5pm to 12am). Green = selected, Gray = available to select.') }}</p>
<div id="disponibilities-grid">
<p class="text-muted">Loading...</p>
<p class="text-muted">{{ _('Loading...') }}</p>
</div>
<div class="form-actions">
<button type="button" class="btn btn-primary" onclick="saveDisponibilities()">
<i class="fas fa-save"></i> Save Disponibilities
</button>
<button type="button" class="btn btn-secondary" onclick="clearDisponibilities()">
<i class="fas fa-trash"></i> Clear All
<button type="button" class="btn btn-secondary" data-action="clear-disponibilities">
<i class="fas fa-trash"></i> {{ _('Clear All') }}
</button>
</div>
</div>
@@ -251,16 +248,16 @@
{% if user.role == 'coach' %}
<div class="card" style="grid-column: 1 / -1;">
<div class="card-header">
<h3><i class="fas fa-clock"></i> My Coaching Availability</h3>
<p class="text-muted small">Select time slots when you're available for One on One sessions (8am to 10pm).</p>
<h3><i class="fas fa-clock"></i> {{ _('My Coaching Availability') }}</h3>
<p class="text-muted small">{{ _('Select time slots when you\'re available for One on One sessions (8am to 10pm).') }}</p>
</div>
<div class="card-body">
<div class="availability-grid" id="availability-grid">
<p class="text-muted">Loading availability grid...</p>
<p class="text-muted">{{ _('Loading availability grid...') }}</p>
</div>
<div class="form-actions mt-3">
<button type="button" class="btn btn-secondary" onclick="clearAllAvailability()">
<i class="fas fa-trash"></i> Clear All
<button type="button" class="btn btn-secondary" data-action="clear-availability">
<i class="fas fa-trash"></i> {{ _('Clear All') }}
</button>
</div>
</div>
@@ -309,7 +306,7 @@
@media (max-width: 768px) { .availability-grid { grid-template-columns: repeat(3, 1fr); } }
@media (max-width: 480px) { .availability-grid { grid-template-columns: 1fr; } }
</style>
<script>
<script nonce="{{ csp_nonce }}">
const COACH_TIME_SLOTS = [];
for (let h = 8; h <= 22; h++) {
for (let m = 0; m < 60; m += 30) {
@@ -400,7 +397,10 @@ function saveCoachAvailability() {
}
fetch('{{ url_for("users.manage_coach_availability") }}', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': '{{ csrf_token() }}'
},
body: JSON.stringify({ slots: slots })
})
.then(r => r.json())
@@ -409,7 +409,7 @@ function saveCoachAvailability() {
const msg = document.createElement('div');
msg.className = 'alert alert-success';
msg.style.marginTop = '10px';
msg.innerHTML = '<i class="fas fa-check"></i> Availability saved!';
msg.innerHTML = '<span><i class="fas fa-check"></i> Availability saved!</span>';
document.getElementById('availability-grid').appendChild(msg);
setTimeout(() => msg.remove(), 3000);
}
@@ -419,7 +419,10 @@ function saveCoachAvailability() {
function clearAllAvailability() {
if (!confirm('Are you sure you want to clear all your availability slots?')) return;
fetch('{{ url_for("users.clear_coach_availability") }}', { method: 'POST' })
fetch('{{ url_for("users.clear_coach_availability") }}', {
method: 'POST',
headers: { 'X-CSRFToken': '{{ csrf_token() }}' }
})
.then(r => r.json())
.then(data => {
if (data.success) {
@@ -437,7 +440,7 @@ document.addEventListener('DOMContentLoaded', function() {
{% endif %}
{% if user.role == 'player' %}
<script>
<script nonce="{{ csp_nonce }}">
// Generate time slots from 5pm (17:00) to 12am (24:00)
var TIME_SLOTS = [];
for (var h = 17; h <= 24; h++) {
@@ -494,7 +497,7 @@ function applySlotAction(block, action) {
function renderDisponibilityGrid() {
var grid = document.getElementById('disponibilities-grid');
grid.innerHTML = '<div style="margin-bottom: 10px;"><strong>Click or click-and-drag to select your available hours</strong></div>';
grid.innerHTML = '<div style="margin-bottom: 10px;"><strong>{{ _('Click or click-and-drag to select your available hours') }}</strong></div>';
var container = document.createElement('div');
container.className = 'disponibility-grid';
@@ -608,7 +611,7 @@ function saveDisponibilities() {
var msg = document.createElement('div');
msg.className = 'alert alert-success';
msg.style.marginTop = '10px';
msg.innerHTML = '<i class="fas fa-check"></i> Disponibilities saved successfully!';
msg.innerHTML = '<span><i class="fas fa-check"></i> Disponibilities saved successfully!</span>';
document.getElementById('disponibilities-grid').appendChild(msg);
setTimeout(function() { msg.remove(); }, 3000);
}
@@ -642,6 +645,14 @@ function clearDisponibilities() {
document.addEventListener('DOMContentLoaded', function() {
renderDisponibilityGrid();
});
// Behaviours are declared in the markup with data-action / data-change and
// dispatched by the delegated listener in main.js. This replaces inline
// onclick attributes, which no CSP nonce is able to authorise.
registerActions({
'clear-disponibilities': clearDisponibilities,
'clear-availability': clearAllAvailability,
});
</script>
{% endif %}
{% endblock %}
{% endblock %}
+108 -43
View File
@@ -1,37 +1,37 @@
{% extends "layouts/base.html" %}
{% block title %}Register - TryoutPro{% endblock %}
{% block title %}{{ _('Register') }} - UdeS team manager{% endblock %}
{% block auth_content %}
<form method="POST" action="{{ url_for('auth.register') }}" class="auth-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<!-- ==================== Personal Info ==================== -->
<h4 class="section-title"><i class="fas fa-id-card"></i> Personal Information</h4>
<h4 class="section-title"><i class="fas fa-id-card"></i> {{ _('Personal Information') }}</h4>
<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"
<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') }}"
value="{{ form_data.get('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"
<label for="username"><i class="fas fa-user"></i> {{ _('Username') }}</label>
<input type="text" id="username" name="username" placeholder="{{ _('Choose a username') }}"
value="{{ form_data.get('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"
<label for="email"><i class="fas fa-envelope"></i> {{ _('Email') }}</label>
<input type="email" id="email" name="email" placeholder="{{ _('Enter your email') }}"
value="{{ form_data.get('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"
<label for="phone"><i class="fas fa-phone"></i> {{ _('Phone (Optional)') }}</label>
<input type="tel" id="phone" name="phone" placeholder="{{ _('Enter your phone number') }}"
value="{{ form_data.get('phone', '') }}">
</div>
<hr class="section-divider">
<!-- ==================== Discord Connection (before games) ==================== -->
<h4 class="section-title"><i class="fab fa-discord"></i> Discord Connection</h4>
<p class="text-muted small">Connect your Discord account to automatically fill your gamertags from your connected game accounts.</p>
<h4 class="section-title"><i class="fab fa-discord"></i> {{ _('Discord Connection') }}</h4>
<p class="text-muted small">{{ _('Connect your Discord account to automatically fill your gamertags from your connected game accounts.') }}</p>
{% set discord_data = session.get('discord_oauth') %}
{% if discord_data %}
@@ -49,30 +49,28 @@
<div class="discord-user-info">
<span class="discord-username">{{ discord_data.username }}</span>
<span class="discord-connected-label text-success">
<i class="fas fa-check-circle"></i> Connected
<i class="fas fa-check-circle"></i> {{ _('Connected') }}
</span>
</div>
</div>
<a href="{{ url_for('auth.discord_login') }}" class="btn btn-sm btn-outline discord-reconnect">
<i class="fas fa-sync-alt"></i> Reconnect
<a href="{{ url_for('auth.discord_login') }}" class="btn btn-sm btn-outline discord-reconnect" data-action="save-form-draft">
<i class="fas fa-sync-alt"></i> {{ _('Reconnect') }}
</a>
</div>
<input type="hidden" name="discord_username" value="{{ discord_data.username }}">
<input type="hidden" name="discord_user_id" value="{{ discord_data.id }}">
<small class="form-text text-success">
<i class="fas fa-check-circle"></i> Discord connected. Game connections have been used to pre-fill your profile below.
<i class="fas fa-check-circle"></i> {{ _('Discord connected. Game connections have been used to pre-fill your profile below.') }}
</small>
</div>
{% else %}
<div class="form-group discord-connect-section">
<a href="{{ url_for('auth.discord_login') }}" class="btn btn-discord btn-block">
<i class="fab fa-discord"></i> Connect Discord Account
<a href="{{ url_for('auth.discord_login') }}" class="btn btn-discord btn-block" data-action="save-form-draft">
<i class="fab fa-discord"></i> {{ _('Connect Discord Account') }}
</a>
<small class="form-text text-muted">Connect to pre-fill your gamertags from Steam, Battle.net, Xbox, etc.</small>
<small class="form-text text-muted">{{ _('Connect to pre-fill your gamertags from Steam, Battle.net, Xbox, etc.') }}</small>
</div>
<div class="form-group">
<label for="discord_username"><i class="fab fa-discord"></i> Discord Username (Manual)</label>
<input type="text" id="discord_username" name="discord_username" placeholder="e.g. YourName"
<label for="discord_username"><i class="fab fa-discord"></i> {{ _('Discord Username (Manual)') }}</label>
<input type="text" id="discord_username" name="discord_username" placeholder="{{ _('e.g. YourName') }}"
value="{{ form_data.get('discord_username', '') }}">
</div>
{% endif %}
@@ -80,11 +78,11 @@
<hr class="section-divider">
<!-- ==================== E-Sports Profile ==================== -->
<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>
<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>
<label><i class="fas fa-headset"></i> {{ _('Games You Play') }}</label>
<div class="checkbox-grid">
{% set selected_games = form_data.get('games', []) %}
{% set auto_select = session.get('discord_oauth', {}).get('auto_select_games', []) %}
@@ -93,12 +91,12 @@
<label class="checkbox-label">
<input type="checkbox" name="games" value="{{ game }}"
{% if is_checked %}checked{% endif %}
onchange="toggleGamertagInput(this)">
data-change="toggle-gamertag">
<span>{{ game }}</span>
</label>
{% endfor %}
</div>
<small class="form-text text-muted">Select all games you're signing in for.</small>
<small class="form-text text-muted">{{ _('Select all games you\'re signing in for.') }}</small>
</div>
<!-- Gamertag fields per game -->
@@ -126,36 +124,41 @@
{% endfor %}
<div class="form-group">
<label for="league_os_profile"><i class="fas fa-link"></i> League OS Connection (Optional)</label>
<label for="league_os_profile"><i class="fas fa-link"></i> {{ _('League OS Connection (Optional)') }}</label>
<input type="text" id="league_os_profile" name="league_os_profile"
placeholder="League OS profile link or ID"
placeholder="{{ _('League OS profile link or ID') }}"
value="{{ form_data.get('league_os_profile', '') }}">
<small class="form-text text-muted">Connect your League OS profile for organized play.</small>
<small class="form-text text-muted">{{ _('Connect your League OS profile for organized play.') }}</small>
</div>
<hr class="section-divider">
<h4 class="section-title"><i class="fas fa-shield-alt"></i> Security</h4>
<h4 class="section-title"><i class="fas fa-shield-alt"></i> {{ _('Security') }}</h4>
<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"
<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"
<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>
<div class="form-group">
<label for="captcha_answer"><i class="fas fa-calculator"></i> {{ captcha.question }}</label>
<input type="number" id="captcha_answer" name="captcha_answer" placeholder="Answer" required autocomplete="off">
{# Honeypot (SEC-AUTH-008). Not a field anyone is meant to see or fill:
hidden in the stylesheet, kept out of the tab order, and told to
screen readers to skip. A submission that carries a value here is a
robot filling every input it can find.
Do not add a label, do not translate the name, do not remove
aria-hidden — each of those turns it into a trap for a person. #}
<div class="honeypot" aria-hidden="true">
<input type="text" name="{{ honeypot_field }}" tabindex="-1" autocomplete="off" value="">
</div>
<button type="submit" class="btn btn-primary btn-block">Create Account</button>
<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>
<script>
<script nonce="{{ csp_nonce }}">
// Toggle gamertag input visibility when a game checkbox is checked/unchecked
function toggleGamertagInput(checkbox) {
var game = checkbox.value.replace(/ /g, '_');
@@ -165,13 +168,75 @@ function toggleGamertagInput(checkbox) {
}
}
// On page load, ensure gamertag groups match checkbox state
// Save form draft to sessionStorage before navigating to Discord OAuth
function saveFormDraft() {
var draft = {};
var form = document.querySelector('.auth-form');
if (!form) return;
var inputs = form.querySelectorAll('input, select, textarea');
inputs.forEach(function(input) {
if (!input.name) return;
if (input.type === 'checkbox') {
if (!draft[input.name]) draft[input.name] = [];
if (input.checked) draft[input.name].push(input.value);
} else if (input.type === 'password') {
// Never save passwords
} else {
draft[input.name] = input.value;
}
});
sessionStorage.setItem('register_form_draft', JSON.stringify(draft));
}
// Restore form draft from sessionStorage on page load
function restoreFormDraft() {
var saved = sessionStorage.getItem('register_form_draft');
if (!saved) return;
try {
var draft = JSON.parse(saved);
var hasServerData = document.querySelector('.auth-form input[name="full_name"]').value !== '';
if (hasServerData) return;
for (var key in draft) {
if (key === 'games') {
var values = draft[key];
var checkboxes = document.querySelectorAll('input[name="games"]');
checkboxes.forEach(function(cb) {
cb.checked = values.indexOf(cb.value) !== -1;
toggleGamertagInput(cb);
});
} else if (key === 'csrf_token' || key.indexOf('password') !== -1) {
// Skip CSRF token and passwords
} else {
var input = document.querySelector('input[name="' + key + '"], textarea[name="' + key + '"]');
if (input) input.value = draft[key];
}
}
} catch(e) {
// Invalid JSON, ignore
}
}
// Clear draft on successful form submission
document.querySelector('.auth-form').addEventListener('submit', function() {
sessionStorage.removeItem('register_form_draft');
});
// On page load, ensure gamertag groups match checkbox state and restore draft
document.addEventListener('DOMContentLoaded', function() {
restoreFormDraft();
var checkboxes = document.querySelectorAll('input[name="games"]');
checkboxes.forEach(function(checkbox) {
toggleGamertagInput(checkbox);
});
});
// Behaviours are declared in the markup with data-action / data-change and
// dispatched by the delegated listener in main.js. This replaces inline
// onclick attributes, which no CSP nonce is able to authorise.
registerActions({
'save-form-draft': saveFormDraft,
'toggle-gamertag': toggleGamertagInput,
});
</script>
<style>
@@ -289,4 +354,4 @@ document.addEventListener('DOMContentLoaded', function() {
color: #57F287 !important;
}
</style>
{% endblock %}
{% endblock %}
+23 -23
View File
@@ -1,5 +1,5 @@
{% extends "layouts/base.html" %}
{% block title %}{% if match %}Edit{% else %}Schedule{% endif %} Team Match - TryoutPro{% endblock %}
{% block title %}{% if match %}Edit{% else %}Schedule{% endif %} Team Match - UdeS team manager{% endblock %}
{% block page_title %}{% if match %}Edit{% else %}Schedule{% endif %} Team Match{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / <a href="{{ url_for('team_matches.list_matches') }}">Team Matches</a> / {% if match %}Edit{% else %}New{% endif %}</span>{% endblock %}
@@ -14,71 +14,71 @@
<div class="form-row">
<div class="form-group col-6">
<label for="title">Match Title</label>
<label for="title">{{ _('Match Title') }}</label>
<input type="text" id="title" name="title" class="form-input"
value="{{ match.title if match else 'Team Match — ' + team.name }}"
placeholder="e.g., Regular Season vs Opponent" required>
placeholder="{{ _('e.g., Regular Season vs Opponent') }}" required>
</div>
{% if not is_practice %}
<div class="form-group col-6">
<label for="opponent">Opponent (optional)</label>
<label for="opponent">{{ _('Opponent (optional)') }}</label>
<input type="text" id="opponent" name="opponent" class="form-input"
value="{{ match.opponent if match else '' }}"
placeholder="e.g., University of Toronto">
placeholder="{{ _('e.g., University of Toronto') }}">
</div>
{% endif %}
</div>
<div class="form-row">
<div class="form-group col-4">
<label for="date">Date</label>
<label for="date">{{ _('Date') }}</label>
<input type="date" id="date" name="date" class="form-input"
value="{{ match.date.strftime('%Y-%m-%d') if match else '' }}"
required>
</div>
<div class="form-group col-4">
<label for="start_time">Start Time</label>
<label for="start_time">{{ _('Start Time') }}</label>
<input type="time" id="start_time" name="start_time" class="form-input"
value="{{ match.start_time.strftime('%H:%M') if match and match.start_time else '' }}">
</div>
<div class="form-group col-4">
<label for="end_time">End Time</label>
<label for="end_time">{{ _('End Time') }}</label>
<input type="time" id="end_time" name="end_time" class="form-input"
value="{{ match.end_time.strftime('%H:%M') if match and match.end_time else '' }}">
<small class="text-muted">Auto-calculated if left empty (start + 30 min)</small>
<small class="text-muted">{{ _('Auto-calculated if left empty (start + 30 min)') }}</small>
</div>
</div>
<div class="form-row">
<div class="form-group col-6">
<label for="location">Location</label>
<label for="location">{{ _('Location') }}</label>
<input type="text" id="location" name="location" class="form-input"
value="{{ match.location if match else '' }}"
placeholder="e.g., Online, Gym A">
placeholder="{{ _('e.g., Online, Gym A') }}">
</div>
{% if match %}
<div class="form-group col-6">
<label for="status">Status</label>
<label for="status">{{ _('Status') }}</label>
<select id="status" name="status" class="form-select">
<option value="scheduled" {% if match.status == 'scheduled' %}selected{% endif %}>Scheduled</option>
<option value="completed" {% if match.status == 'completed' %}selected{% endif %}>Completed</option>
<option value="cancelled" {% if match.status == 'cancelled' %}selected{% endif %}>Cancelled</option>
<option value="scheduled" {% if match.status == 'scheduled' %}selected{% endif %}>{{ _('Scheduled') }}</option>
<option value="completed" {% if match.status == 'completed' %}selected{% endif %}>{{ _('Completed') }}</option>
<option value="cancelled" {% if match.status == 'cancelled' %}selected{% endif %}>{{ _('Cancelled') }}</option>
</select>
</div>
{% endif %}
</div>
<div class="form-group">
<label for="description">Description (optional)</label>
<label for="description">{{ _('Description (optional)') }}</label>
<textarea id="description" name="description" class="form-input" rows="3"
placeholder="Additional details about the match...">{{ match.description if match else '' }}</textarea>
placeholder="{{ _('Additional details about the match...') }}">{{ match.description if match else '' }}</textarea>
</div>
{% if not match %}
<!-- Player roster (pre-filled, read-only display) -->
<div class="card mt-4">
<div class="card-header">
<h4><i class="fas fa-users"></i> Team Roster (auto-included)</h4>
<h4><i class="fas fa-users"></i> {{ _('Team Roster (auto-included)') }}</h4>
<span class="text-muted" style="font-size: 0.85rem;">All {{ team_players | length }} player(s) will be added automatically</span>
</div>
<div class="card-body">
@@ -95,7 +95,7 @@
</div>
{% else %}
<p class="text-muted text-center py-3">
<i class="fas fa-users-slash"></i> No players on this team. Add players in the Teams page first.
<i class="fas fa-users-slash"></i> {{ _('No players on this team. Add players in the Teams page first.') }}
</p>
{% endif %}
</div>
@@ -104,15 +104,15 @@
<!-- Edit mode: show participants with presence status -->
<div class="card mt-4">
<div class="card-header">
<h4><i class="fas fa-users"></i> Participants</h4>
<h4><i class="fas fa-users"></i> {{ _('Participants') }}</h4>
</div>
<div class="card-body">
{% if match.participants %}
<table class="table">
<thead>
<tr>
<th>Player</th>
<th>Presence</th>
<th>{{ _('Player') }}</th>
<th>{{ _('Presence') }}</th>
</tr>
</thead>
<tbody>
@@ -131,7 +131,7 @@
</tbody>
</table>
{% else %}
<p class="text-muted text-center">No participants recorded.</p>
<p class="text-muted text-center">{{ _('No participants recorded.') }}</p>
{% endif %}
</div>
</div>
+32 -23
View File
@@ -1,13 +1,14 @@
{% extends "layouts/base.html" %}
{% block title %}Team Matches - TryoutPro{% endblock %}
{% block page_title %}Team Matches{% endblock %}
{% import 'layouts/_pagination.html' as pager %}
{% block title %}{{ _('Team Matches') }} - UdeS team manager{% endblock %}
{% block page_title %}{{ _('Team Matches') }}{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / Team Matches</span>{% endblock %}
{% block header_actions %}
{% if teams %}
<div class="header-actions">
<select id="teamSelect" class="form-select" style="width:200px;" onchange="window.location.href='/team-matches/' + this.value + '/create'">
<option value="">+ Schedule Match</option>
<select id="teamSelect" class="form-select" style="width:200px;" data-navigate="/team-matches/{value}/create">
<option value="">{{ _('+ Schedule Match') }}</option>
{% for t in teams %}
<option value="{{ t.id }}">{{ t.name }}</option>
{% endfor %}
@@ -22,15 +23,15 @@
<table class="table">
<thead>
<tr>
<th>Match</th>
<th>Team</th>
<th>Opponent</th>
<th>Date</th>
<th>Time</th>
<th>Location</th>
<th>Presence</th>
<th>Status</th>
<th>Actions</th>
<th>{{ _('Match') }}</th>
<th>{{ _('Team') }}</th>
<th>{{ _('Opponent') }}</th>
<th>{{ _('Date') }}</th>
<th>{{ _('Time') }}</th>
<th>{{ _('Location') }}</th>
<th>{{ _('Presence') }}</th>
<th>{{ _('Status') }}</th>
<th>{{ _('Actions') }}</th>
</tr>
</thead>
<tbody>
@@ -94,12 +95,12 @@
<td class="eval-actions">
{% set can_manage_this = (current_user.role in ['admin', 'manager']) or (current_user.role == 'coach' and m.org_team.coaches.filter_by(id=current_user.id).first()) or (current_user.role == 'coach' and m.org_team.coach_id == current_user.id) %}
{% if can_manage_this %}
<a href="{{ url_for('team_matches.edit_match', match_id=m.id) }}" class="btn btn-sm btn-outline" title="Edit Match">
<a href="{{ url_for('team_matches.edit_match', match_id=m.id) }}" class="btn btn-sm btn-outline" title="{{ _('Edit Match') }}">
<i class="fas fa-edit"></i>
</a>
<form method="POST" action="{{ url_for('team_matches.delete_match', match_id=m.id) }}" class="inline-form" onsubmit="return confirm('Delete this match?')">
<form method="POST" action="{{ url_for('team_matches.delete_match', match_id=m.id) }}" class="inline-form" data-confirm="{{ _('Delete this match?') }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<button type="submit" class="btn btn-sm btn-danger" title="Delete Match">
<button type="submit" class="btn btn-sm btn-danger" title="{{ _('Delete Match') }}">
<i class="fas fa-trash"></i>
</button>
</form>
@@ -111,7 +112,7 @@
<button class="btn btn-sm {% if p.is_confirmed %}btn-success{% else %}btn-outline{% endif %} presence-toggle-btn"
data-match-id="{{ m.id }}"
data-participant-id="{{ p.id }}"
onclick="togglePresence(this)">
data-action="toggle-presence">
{% if p.is_confirmed %}✅ Confirmed{% else %}Confirm{% endif %}
</button>
{% endif %}
@@ -122,18 +123,19 @@
{% endfor %}
</tbody>
</table>
{{ pager.controls(pagination) }}
</div>
{% else %}
<div class="card">
<div class="card-body text-center py-5">
<div class="empty-state">
<i class="fas fa-futbol fa-3x text-muted mb-3"></i>
<h3>No Team Matches</h3>
<p class="text-muted">Regular season matches have not been scheduled yet.</p>
<h3>{{ _('No Team Matches') }}</h3>
<p class="text-muted">{{ _('Regular season matches have not been scheduled yet.') }}</p>
{% if teams %}
<div class="mt-3">
<select id="teamSelectEmpty" class="form-select" style="width:220px; display:inline;" onchange="window.location.href='/team-matches/' + this.value + '/create'">
<option value="">-- Schedule a Match --</option>
<select id="teamSelectEmpty" class="form-select" style="width:220px; display:inline;" data-navigate="/team-matches/{value}/create">
<option value="">{{ _('-- Schedule a Match --') }}</option>
{% for t in teams %}
<option value="{{ t.id }}">{{ t.name }}</option>
{% endfor %}
@@ -145,7 +147,7 @@
</div>
{% endif %}
<script>
<script nonce="{{ csp_nonce }}">
function togglePresence(btn) {
var matchId = btn.getAttribute('data-match-id');
var participantId = btn.getAttribute('data-participant-id');
@@ -175,6 +177,13 @@ function togglePresence(btn) {
console.error('Error:', error);
});
}
// Behaviours are declared in the markup with data-action / data-change and
// dispatched by the delegated listener in main.js. This replaces inline
// onclick attributes, which no CSP nonce is able to authorise.
registerActions({
'toggle-presence': togglePresence,
});
</script>
<style>
@@ -227,4 +236,4 @@ function togglePresence(btn) {
margin-left: 4px;
}
</style>
{% endblock %}
{% endblock %}
+11 -11
View File
@@ -1,12 +1,12 @@
{% extends "layouts/base.html" %}
{% block title %}Team Notes - TryoutPro{% endblock %}
{% block page_title %}Team Notes{% endblock %}
{% block title %}{{ _('Team Notes') }} - UdeS team manager{% endblock %}
{% block page_title %}{{ _('Team Notes') }}{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / Team Notes</span>{% endblock %}
{% block content %}
<div class="card">
<div class="card-header">
<h3><i class="fas fa-users"></i> Team Improvement Notes</h3>
<h3><i class="fas fa-users"></i> {{ _('Team Improvement Notes') }}</h3>
{% if org_team %}
<span class="badge badge-esport">{{ org_team.name }}</span>
{% endif %}
@@ -16,14 +16,14 @@
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="form-group">
<label for="content">Team Notes Content</label>
<textarea name="content" id="content" class="form-textarea" rows="6" placeholder="Enter improvement suggestions and notes for your team...">{{ team_notes[0].content if team_notes else '' }}</textarea>
<p class="form-text">These notes will be visible to all players on your team.</p>
<label for="content">{{ _('Team Notes Content') }}</label>
<textarea name="content" id="content" class="form-textarea" rows="6" placeholder="{{ _('Enter improvement suggestions and notes for your team...') }}">{{ team_notes[0].content if team_notes else '' }}</textarea>
<p class="form-text">{{ _('These notes will be visible to all players on your team.') }}</p>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary">
<i class="fas fa-plus"></i> Add Team Notes
<i class="fas fa-plus"></i> {{ _('Add Team Notes') }}
</button>
</div>
</form>
@@ -33,14 +33,14 @@
{% if team_notes %}
<div class="card mt-4">
<div class="card-header">
<h3><i class="fas fa-history"></i> Note History</h3>
<h3><i class="fas fa-history"></i> {{ _('Note History') }}</h3>
</div>
<div class="card-body">
<table class="table">
<thead>
<tr>
<th>Last Updated</th>
<th>Content Preview</th>
<th>{{ _('Last Updated') }}</th>
<th>{{ _('Content Preview') }}</th>
</tr>
</thead>
<tbody>
@@ -55,4 +55,4 @@
</div>
</div>
{% endif %}
{% endblock %}
{% endblock %}
+73 -61
View File
@@ -1,12 +1,12 @@
{% extends "layouts/base.html" %}
{% block title %}Teams - TryoutPro{% endblock %}
{% block page_title %}Teams{% endblock %}
{% block title %}{{ _('Teams') }} - UdeS team manager{% 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 class="btn btn-primary" data-action="show-create-form">
<i class="fas fa-plus"></i> {{ _('New Team') }}
</button>
{% endif %}
{% endblock %}
@@ -15,29 +15,29 @@
{% 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>
<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-4">
<label for="name">Team Name</label>
<input type="text" id="name" name="name" placeholder="e.g., Varsity, JV, U14" required>
<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-4">
<label for="coach_id">Assigned Coach</label>
<label for="coach_id">{{ _('Assigned Coach') }}</label>
<select id="coach_id" name="coach_id" class="form-select">
<option value="">-- No coach assigned --</option>
<option value="">{{ _('-- No coach assigned --') }}</option>
{% for coach in coaches %}
<option value="{{ coach.id }}">{{ coach.username }}</option>
{% endfor %}
</select>
</div>
<div class="form-group col-4">
<label for="manager_id">Assigned Manager</label>
<label for="manager_id">{{ _('Assigned Manager') }}</label>
<select id="manager_id" name="manager_id" class="form-select">
<option value="">-- No manager assigned --</option>
<option value="">{{ _('-- No manager assigned --') }}</option>
{% for manager in managers %}
<option value="{{ manager.id }}">{{ manager.username }}</option>
{% endfor %}
@@ -45,8 +45,8 @@
</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>
<button type="button" class="btn btn-secondary" data-action="hide-create-form">{{ _('Cancel') }}</button>
<button type="submit" class="btn btn-primary">{{ _('Create Team') }}</button>
</div>
</form>
</div>
@@ -61,18 +61,18 @@
<div class="card-actions">
{% 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 '' }}" data-manager-id="{{ team.manager_id or '' }}">
<i class="fas fa-edit"></i> Edit
<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.')">
<form method="POST" action="{{ url_for('teams.delete_team', team_id=team.id) }}" class="inline-form" data-confirm="{{ _('Delete team %(name)s? It will be unassigned from any linked tryouts.', name=team.name) }}">
<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
<i class="fas fa-trash"></i> {{ _('Delete') }}
</button>
</form>
{% endif %}
{% if current_user.role == 'coach' and team.coach_id == current_user.id %}
<a href="{{ url_for('users.notes_dashboard') }}" class="btn btn-sm btn-primary" title="Add Notes">
<i class="fas fa-sticky-note"></i> Notes
<a href="{{ url_for('users.notes_dashboard') }}" class="btn btn-sm btn-primary" title="{{ _('Add Notes') }}">
<i class="fas fa-sticky-note"></i> {{ _('Notes') }}
</a>
{% endif %}
</div>
@@ -81,7 +81,7 @@
<div class="team-staff-bar">
<div class="staff-row">
<div class="staff-group">
<span class="staff-label"><i class="fas fa-chalkboard-teacher"></i> Coaches</span>
<span class="staff-label"><i class="fas fa-chalkboard-teacher"></i> {{ _('Coaches') }}</span>
<div class="staff-items">
{% set team_coaches = team.get_coaches() %}
{% if team_coaches %}
@@ -89,10 +89,10 @@
<span class="staff-tag">
{{ c.username }}
{% if can_manage %}
<form method="POST" action="{{ url_for('teams.remove_coach', team_id=team.id) }}" class="inline-form" onsubmit="return confirm('Remove coach {{ c.username }} from {{ team.name }}?')">
<form method="POST" action="{{ url_for('teams.remove_coach', team_id=team.id) }}" class="inline-form" data-confirm="{{ _('Remove coach %(coach)s from %(team)s?', coach=c.username, team=team.name) }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<input type="hidden" name="coach_id" value="{{ c.id }}"/>
<button type="submit" class="btn-icon-sm" title="Remove Coach">&times;</button>
<button type="submit" class="btn-icon-sm" title="{{ _('Remove Coach') }}">&times;</button>
</form>
{% endif %}
</span>
@@ -103,7 +103,7 @@
</div>
</div>
<div class="staff-group">
<span class="staff-label"><i class="fas fa-user-tie"></i> Managers</span>
<span class="staff-label"><i class="fas fa-user-tie"></i> {{ _('Managers') }}</span>
<div class="staff-items">
{% set team_managers = team.get_managers() %}
{% if team_managers %}
@@ -111,10 +111,10 @@
<span class="staff-tag manager-tag">
{{ m.username }}
{% if can_manage %}
<form method="POST" action="{{ url_for('teams.remove_manager', team_id=team.id) }}" class="inline-form" onsubmit="return confirm('Remove manager {{ m.username }} from {{ team.name }}?')">
<form method="POST" action="{{ url_for('teams.remove_manager', team_id=team.id) }}" class="inline-form" data-confirm="{{ _('Remove manager %(manager)s from %(team)s?', manager=m.username, team=team.name) }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<input type="hidden" name="manager_id" value="{{ m.id }}"/>
<button type="submit" class="btn-icon-sm" title="Remove Manager">&times;</button>
<button type="submit" class="btn-icon-sm" title="{{ _('Remove Manager') }}">&times;</button>
</form>
{% endif %}
</span>
@@ -124,15 +124,15 @@
{% endif %}
</div>
</div>
<a href="{{ url_for('team_matches.list_matches') }}?team_id={{ team.id }}" class="btn btn-sm btn-outline" title="View Team Matches">
<i class="fas fa-futbol"></i> Matches
<a href="{{ url_for('team_matches.list_matches') }}?team_id={{ team.id }}" class="btn btn-sm btn-outline" title="{{ _('View Team Matches') }}">
<i class="fas fa-futbol"></i> {{ _('Matches') }}
</a>
{% if current_user.can_schedule_matches() and (current_user.role in ['admin', 'manager'] or (current_user.role == 'coach' and team.coaches.filter_by(id=current_user.id).first()) or (current_user.role == 'coach' and team.coach_id == current_user.id)) %}
<a href="{{ url_for('team_matches.create_match', team_id=team.id) }}" class="btn btn-sm btn-success" title="Schedule Team Match">
<i class="fas fa-plus"></i> Match
<a href="{{ url_for('team_matches.create_match', team_id=team.id) }}" class="btn btn-sm btn-success" title="{{ _('Schedule Team Match') }}">
<i class="fas fa-plus"></i> {{ _('Match') }}
</a>
<a href="{{ url_for('team_matches.create_match', team_id=team.id, type='practice') }}" class="btn btn-sm btn-info" title="Schedule Practice">
<i class="fas fa-dumbbell"></i> Practice
<a href="{{ url_for('team_matches.create_match', team_id=team.id, type='practice') }}" class="btn btn-sm btn-info" title="{{ _('Schedule Practice') }}">
<i class="fas fa-dumbbell"></i> {{ _('Practice') }}
</a>
{% endif %}
</div>
@@ -142,13 +142,13 @@
<table class="table">
<thead>
<tr>
<th>Player</th>
<th>Status</th>
<th>Position / Role</th>
<th>Email</th>
<th>Phone</th>
<th>{{ _('Player') }}</th>
<th>{{ _('Status') }}</th>
<th>{{ _('Position / Role') }}</th>
<th>{{ _('Email') }}</th>
<th>{{ _('Phone') }}</th>
{% if can_manage_team %}
<th>Actions</th>
<th>{{ _('Actions') }}</th>
{% endif %}
</tr>
</thead>
@@ -166,7 +166,7 @@
<button class="btn btn-sm status-toggle-btn {% if entry.status == 'starter' %}btn-success{% else %}btn-warning{% endif %}"
data-team-id="{{ team.id }}"
data-player-id="{{ entry.player.id }}"
onclick="toggleStatus(this)">
data-action="toggle-status">
{{ entry.status | capitalize }}
</button>
{% else %}
@@ -180,10 +180,10 @@
<td>{{ entry.player.phone or '-' }}</td>
{% if can_manage_team %}
<td>
<form method="POST" action="{{ url_for('teams.remove_player', team_id=team.id, player_id=entry.player.id) }}" class="inline-form" onsubmit="return confirm('Remove {{ entry.player.username }} from {{ team.name }}?')">
<form method="POST" action="{{ url_for('teams.remove_player', team_id=team.id, player_id=entry.player.id) }}" class="inline-form" data-confirm="{{ _('Remove %(player)s from %(team)s?', player=entry.player.username, team=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
<i class="fas fa-user-minus"></i> {{ _('Remove') }}
</button>
</form>
</td>
@@ -194,9 +194,9 @@
<td colspan="{% if can_manage_team %}6{% else %}5{% endif %}" class="text-center">
<div class="empty-state">
<i class="fas fa-users-slash"></i>
<h4>No players assigned</h4>
<h4>{{ _('No players assigned') }}</h4>
{% if can_manage_team %}
<p>Add players to this team using the form below.</p>
<p>{{ _('Add players to this team using the form below.') }}</p>
{% endif %}
</div>
</td>
@@ -213,7 +213,7 @@
<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>
<option value="">{{ _('-- Select a player --') }}</option>
{% for p in all_players %}
{% if p.id not in team.players | map(attribute='id') %}
<option value="{{ p.id }}">{{ p.username }}</option>
@@ -221,11 +221,11 @@
{% endfor %}
</select>
<select name="status" class="form-select ml-2">
<option value="starter">Starter</option>
<option value="substitute">Substitute</option>
<option value="starter">{{ _('Starter') }}</option>
<option value="substitute">{{ _('Substitute') }}</option>
</select>
<button type="submit" class="btn btn-sm btn-primary ml-2">
<i class="fas fa-plus"></i> Add to Team
<i class="fas fa-plus"></i> {{ _('Add to Team') }}
</button>
</form>
</div>
@@ -237,12 +237,12 @@
<div class="card-body text-center">
<div class="empty-state">
<i class="fas fa-users-cog"></i>
<h3>No teams yet</h3>
<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>
<p>{{ _('Create organization teams and assign coaches to manage tryouts.') }}</p>
<button class="btn btn-primary" data-action="show-create-form">{{ _('Create Team') }}</button>
{% else %}
<p>There are no teams to display.</p>
<p>{{ _('There are no teams to display.') }}</p>
{% endif %}
</div>
</div>
@@ -251,11 +251,11 @@
<!-- Edit Team Modal -->
<div id="editTeamModal" class="modal hidden">
<div class="modal-backdrop" onclick="hideEditForm()"></div>
<div class="modal-backdrop" data-action="hide-edit-form"></div>
<div class="modal-content">
<div class="modal-header">
<h3>Edit Team</h3>
<button class="modal-close" onclick="hideEditForm()">&times;</button>
<h3>{{ _('Edit Team') }}</h3>
<button class="modal-close" data-action="hide-edit-form">&times;</button>
</div>
<div class="modal-body">
<form id="editTeamForm" method="POST" action="" class="form">
@@ -263,44 +263,47 @@
<input type="hidden" name="sync_staff" value="1"/>
<div class="form-group">
<label for="edit_name">Team Name</label>
<label for="edit_name">{{ _('Team Name') }}</label>
<input type="text" id="edit_name" name="name" required>
</div>
<hr class="section-divider">
<!-- Manage Coaches -->
<h5 class="mb-2"><i class="fas fa-chalkboard-teacher"></i> Coaches</h5>
<h5 class="mb-2"><i class="fas fa-chalkboard-teacher"></i> {{ _('Coaches') }}</h5>
<div class="form-group">
<select name="coach_ids" id="edit-coach-select" class="form-select" multiple style="min-height: 100px; width: 100%;">
{% for coach in coaches %}
<option value="{{ coach.id }}" class="coach-option">{{ coach.username }}</option>
{% endfor %}
</select>
<small class="form-text">Hold Ctrl/Cmd to select multiple. Only unassigned coaches shown.</small>
<small class="form-text">{{ _('Hold Ctrl/Cmd to select multiple. Only unassigned coaches shown.') }}</small>
</div>
<!-- Manage Managers -->
<h5 class="mb-2"><i class="fas fa-user-tie"></i> Managers</h5>
<h5 class="mb-2"><i class="fas fa-user-tie"></i> {{ _('Managers') }}</h5>
<div class="form-group">
<select name="manager_ids" id="edit-manager-select" class="form-select" multiple style="min-height: 100px; width: 100%;">
{% for manager in managers %}
<option value="{{ manager.id }}" class="manager-option">{{ manager.username }}</option>
{% endfor %}
</select>
<small class="form-text">Hold Ctrl/Cmd to select multiple. Only unassigned managers shown.</small>
<small class="form-text">{{ _('Hold Ctrl/Cmd to select multiple. Only unassigned managers shown.') }}</small>
</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>
<button type="button" class="btn btn-secondary" data-action="hide-edit-form">{{ _('Cancel') }}</button>
<button type="submit" class="btn btn-primary">{{ _('Save Changes') }}</button>
</div>
</form>
</div>
</div>
</div>
<script>
{% endblock %}
{% block scripts %}
<script nonce="{{ csp_nonce }}">
function showCreateForm() {
document.getElementById('createTeamForm').classList.remove('hidden');
}
@@ -405,6 +408,15 @@ function populateEditSelects(teamId) {
function hideEditForm() {
document.getElementById('editTeamModal').classList.add('hidden');
}
// Behaviours declared in the markup, dispatched by the delegated listener
// in main.js. Inline onclick attributes cannot be authorised by a CSP nonce.
registerActions({
'show-create-form': showCreateForm,
'hide-create-form': hideCreateForm,
'hide-edit-form': hideEditForm,
'toggle-status': toggleStatus,
});
</script>
<style>
/* === Full-width staff bar layout === */
@@ -539,4 +551,4 @@ function hideEditForm() {
color: var(--text-primary);
}
</style>
{% endblock %}
{% endblock %}
+32 -25
View File
@@ -1,5 +1,5 @@
{% extends "layouts/base.html" %}
{% block title %}{% if tryout %}Edit Tryout{% else %}Create Tryout{% endif %} - TryoutPro{% endblock %}
{% block title %}{% if tryout %}Edit Tryout{% else %}Create Tryout{% endif %} - UdeS team manager{% endblock %}
{% block page_title %}{% if tryout %}Edit Tryout{% else %}Create Tryout{% endif %}{% endblock %}
{% block breadcrumb %}
<span class="breadcrumb">
@@ -20,45 +20,45 @@
<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 if tryout else '' }}" placeholder="e.g., Spring Season Tryouts" required>
<label for="title">{{ _('Tryout Title') }}</label>
<input type="text" id="title" name="title" value="{{ tryout.title if tryout else '' }}" placeholder="{{ _('e.g., Spring Season Tryouts') }}" required>
</div>
</div>
<div class="form-row">
<div class="form-group col-6">
<label for="game">Game</label>
<label for="game">{{ _('Game') }}</label>
<select id="game" name="game" class="form-select" required>
<option value="">-- Select a game --</option>
<option value="">{{ _('-- Select a game --') }}</option>
{% for game in esport_games %}
<option value="{{ game }}" {% if tryout and tryout.game == game %}selected{% endif %}>{{ game }}</option>
{% endfor %}
</select>
</div>
<div class="form-group col-3">
<label for="date">Start Date</label>
<label for="date">{{ _('Start Date') }}</label>
<input type="date" id="date" name="date" value="{{ tryout.date.strftime('%Y-%m-%d') if tryout else '' }}" required>
</div>
<div class="form-group col-3">
<label for="end_date">End Date</label>
<label for="end_date">{{ _('End Date') }}</label>
<input type="date" id="end_date" name="end_date" value="{{ tryout.end_date.strftime('%Y-%m-%d') if tryout and tryout.end_date else '' }}">
<small class="text-muted">Optional. Leave blank for single-day tryout.</small>
<small class="text-muted">{{ _('Optional. Leave blank for single-day tryout.') }}</small>
</div>
</div>
<div class="form-row">
<div class="form-group col-6">
<label for="location">Location</label>
<input type="text" id="location" name="location" value="{{ tryout.location or '' if tryout else '' }}" placeholder="e.g., Online">
<label for="location">{{ _('Location') }}</label>
<input type="text" id="location" name="location" value="{{ tryout.location or '' if tryout else '' }}" placeholder="{{ _('e.g., Online') }}">
</div>
<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 '' if tryout else '' }}" placeholder="Leave blank for unlimited" min="1">
<label for="max_players">{{ _('Max Players') }}</label>
<input type="number" id="max_players" name="max_players" value="{{ tryout.max_players or '' if tryout else '' }}" placeholder="{{ _('Leave blank for unlimited') }}" min="1">
</div>
</div>
<div class="form-row">
<div class="form-group col-6">
<label for="target_org_team_id">Target Team</label>
<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>
<option value="">{{ _('-- No target team --') }}</option>
{% for team in org_teams %}
<option value="{{ team.id }}" {% if tryout and tryout.target_org_team_id == team.id %}selected{% endif %}>
{{ team.name }}{% if team.coach %} (Coach: {{ team.coach.username }}){% endif %}
@@ -67,20 +67,27 @@
</select>
</div>
<div class="form-group col-6">
<label for="manager_id">Assigned Manager</label>
<select id="manager_id" name="manager_id" class="form-select">
<option value="">-- No manager assigned --</option>
<label>{{ _('Assigned Managers') }}</label>
<div class="checkbox-grid">
{% for manager in managers %}
<option value="{{ manager.id }}" {% if tryout and tryout.manager_id == manager.id %}selected{% endif %}>
{{ manager.username }}
</option>
{% set is_checked = false %}
{% if tryout %}
{% for m in tryout.get_managers() %}
{% if m.id == manager.id %}{% set is_checked = true %}{% endif %}
{% endfor %}
{% endif %}
<label class="checkbox-label">
<input type="checkbox" name="manager_ids" value="{{ manager.id }}" {% if is_checked %}checked{% endif %}>
<span>{{ manager.username }}</span>
</label>
{% endfor %}
</select>
</div>
<small class="text-muted">{{ _('Select one or more managers for this tryout.') }}</small>
</div>
</div>
<div class="form-row">
<div class="form-group col-12">
<label>Assigned Coaches</label>
<label>{{ _('Assigned Coaches') }}</label>
<div class="checkbox-grid">
{% for coach in coaches %}
{% set is_checked = false %}
@@ -96,12 +103,12 @@
</label>
{% endfor %}
</div>
<small class="text-muted">Select one or more coaches for this tryout.</small>
<small class="text-muted">{{ _('Select one or more coaches for this tryout.') }}</small>
</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...">{% if tryout %}{{ tryout.description or '' }}{% endif %}</textarea>
<label for="description">{{ _('Description') }}</label>
<textarea id="description" name="description" rows="4" placeholder="{{ _('Enter any details about the tryout...') }}">{% if tryout %}{{ tryout.description or '' }}{% endif %}</textarea>
</div>
<div class="form-actions">
<a href="{% if tryout %}{{ url_for('tryouts.view_tryout', tryout_id=tryout.id) }}{% else %}{{ url_for('tryouts.list_tryouts') }}{% endif %}" class="btn btn-secondary">
+7 -7
View File
@@ -1,12 +1,12 @@
{% extends "layouts/base.html" %}
{% block title %}Tryouts - TryoutPro{% endblock %}
{% block page_title %}Tryouts{% endblock %}
{% block title %}{{ _('Tryouts') }} - UdeS team manager{% 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
<i class="fas fa-plus"></i> {{ _('New Tryout') }}
</a>
{% endif %}
{% endblock %}
@@ -77,11 +77,11 @@
</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
<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
<i class="fas fa-clipboard-check"></i> {{ _('Evaluate') }}
</a>
{% endif %}
</div>
@@ -91,9 +91,9 @@
<div class="card-body text-center">
<div class="empty-state">
<i class="fas fa-calendar-times"></i>
<h3>No tryouts found</h3>
<h3>{{ _('No tryouts found') }}</h3>
{% if current_user.can_manage_tryouts() %}
<p>Get started by creating a new tryout.</p>
<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>
+11 -11
View File
@@ -1,21 +1,21 @@
{% extends "layouts/base.html" %}
{% block title %}Upload Contract - TryoutPro{% endblock %}
{% block page_title %}Upload Contract{% endblock %}
{% block title %}{{ _('Upload Contract') }} - UdeS team manager{% endblock %}
{% block page_title %}{{ _('Upload Contract') }}{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / <a href="{{ url_for('users.list_contracts') }}">Contracts</a> / Upload</span>{% endblock %}
{% block content %}
<div class="card">
<div class="card-header">
<h3><i class="fas fa-upload"></i> Upload Contract for Player</h3>
<h3><i class="fas fa-upload"></i> {{ _('Upload Contract for Player') }}</h3>
</div>
<div class="card-body">
<form method="POST" action="{{ url_for('users.upload_contract') }}" enctype="multipart/form-data" class="form">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="form-group">
<label for="player_id">Select Player</label>
<label for="player_id">{{ _('Select Player') }}</label>
<select id="player_id" name="player_id" class="form-select" required>
<option value="">-- Select a player --</option>
<option value="">{{ _('-- Select a player --') }}</option>
{% for player in players %}
<option value="{{ player.id }}">{{ player.username }}{% if player.org_team %} - {{ player.org_team.name }}{% endif %}</option>
{% endfor %}
@@ -23,23 +23,23 @@
</div>
<div class="form-group">
<label for="contract_file">Contract File</label>
<label for="contract_file">{{ _('Contract File') }}</label>
<input type="file" id="contract_file" name="contract_file" accept=".pdf,.doc,.docx,.jpg,.jpeg,.png" required>
<small class="form-text text-muted">Accepted formats: PDF, DOC, DOCX, JPG, PNG</small>
<small class="form-text text-muted">{{ _('Accepted formats: PDF, DOC, DOCX, JPG, PNG') }}</small>
</div>
<div class="form-group">
<label for="notes">Notes (Optional)</label>
<textarea id="notes" name="notes" rows="3" placeholder="Add any notes about this contract..."></textarea>
<label for="notes">{{ _('Notes (Optional)') }}</label>
<textarea id="notes" name="notes" rows="3" placeholder="{{ _('Add any notes about this contract...') }}"></textarea>
</div>
<div class="form-actions">
<a href="{{ url_for('users.list_contracts') }}" class="btn btn-secondary">Cancel</a>
<button type="submit" class="btn btn-primary">
<i class="fas fa-upload"></i> Upload Contract
<i class="fas fa-upload"></i> {{ _('Upload Contract') }}
</button>
</div>
</form>
</div>
</div>
{% endblock %}
{% endblock %}
+15 -13
View File
@@ -1,11 +1,12 @@
{% extends "layouts/base.html" %}
{% block title %}Manage Users - TryoutPro{% endblock %}
{% block page_title %}Manage Users{% endblock %}
{% import 'layouts/_pagination.html' as pager %}
{% block title %}{{ _('Manage Users') }} - UdeS team manager{% 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
<i class="fas fa-user-plus"></i> {{ _('Add User') }}
</a>
{% endblock %}
@@ -16,13 +17,13 @@
<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>
<th>{{ _('Name') }}</th>
<th>{{ _('Username') }}</th>
<th>{{ _('Email') }}</th>
<th>{{ _('Role') }}</th>
<th>{{ _('Status') }}</th>
<th>{{ _('Joined') }}</th>
<th>{{ _('Actions') }}</th>
</tr>
</thead>
<tbody>
@@ -47,10 +48,10 @@
<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
<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.username }}? This cannot be undone.');">
<form method="POST" action="{{ url_for('users.delete_user', user_id=u.id) }}" class="inline-form" data-confirm="{{ _('Delete %(name)s? This cannot be undone.', name=u.username) }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<button type="submit" class="btn btn-sm btn-danger">
<i class="fas fa-trash"></i>
@@ -63,6 +64,7 @@
</tbody>
</table>
</div>
{{ pager.controls(pagination) }}
</div>
</div>
{% endblock %}
{% endblock %}
+101 -81
View File
@@ -1,5 +1,5 @@
{% extends "layouts/base.html" %}
{% block title %}{{ tryout.title }} - TryoutPro{% endblock %}
{% block title %}{{ tryout.title }} - UdeS team manager{% 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 %}
@@ -7,33 +7,33 @@
<div class="tryout-detail">
<div class="card mb-4">
<div class="card-header">
<h3>Tryout Details</h3>
<h3>{{ _('Tryout Details') }}</h3>
<div class="card-actions">
{% if can_edit and not tryout.is_ended %}
<a href="{{ url_for('matches.create_match', tryout_id=tryout.id) }}" class="btn btn-sm btn-success">
<i class="fas fa-futbol"></i> Schedule Match
<i class="fas fa-futbol"></i> {{ _('Schedule Match') }}
</a>
{% endif %}
{% if can_edit %}
<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 name="status" data-submit-on-change 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 and not tryout.is_ended %}
<a href="{{ url_for('tryouts.edit_tryout', tryout_id=tryout.id) }}" class="btn btn-sm btn-primary">
<i class="fas fa-edit"></i> Edit
<i class="fas fa-edit"></i> {{ _('Edit') }}
</a>
{% endif %}
{% if can_edit %}
<form method="POST" action="{{ url_for('tryouts.delete_tryout', tryout_id=tryout.id) }}" class="inline-form" onsubmit="return confirm('Are you sure you want to delete this entire tryout? This will remove all matches, teams, registrations, and evaluations.');">
<form method="POST" action="{{ url_for('tryouts.delete_tryout', tryout_id=tryout.id) }}" class="inline-form" data-confirm="{{ _('Delete this entire tryout? This removes all its matches, teams, registrations and evaluations.') }}">
<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 Tryout
<i class="fas fa-trash"></i> {{ _('Delete Tryout') }}
</button>
</form>
{% endif %}
@@ -70,16 +70,22 @@
<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">Manager</span>
<span class="detail-value">{{ tryout.manager.username if tryout.manager else 'Not assigned' }}</span>
<span class="detail-label">Manager(s)</span>
<span class="detail-value">
{% set mgrs = tryout.get_managers() %}
{% if mgrs %}
{{ mgrs | map(attribute='username') | join(', ') }}
{% else %}
Not assigned
{% endif %}
</span>
</div>
<div class="detail-item">
<span class="detail-label">Coaches</span>
<span class="detail-value">
{% if tryout.coaches %}
{{ tryout.coaches | map(attribute='username') | join(', ') }}
{% elif tryout.coach %}
{{ tryout.coach.username }}
{% set cos = tryout.get_coaches() %}
{% if cos %}
{{ cos | map(attribute='username') | join(', ') }}
{% else %}
Not assigned
{% endif %}
@@ -105,7 +111,7 @@
<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
<i class="fas fa-user-plus"></i> {{ _('Register for this Tryout') }}
</button>
</form>
</div>
@@ -115,20 +121,20 @@
{% if can_edit and not tryout.is_ended %}
<div class="card mb-4">
<div class="card-header">
<h3><i class="fas fa-user-plus"></i> Add Player to Tryout</h3>
<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>
<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.username }}{% 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
<i class="fas fa-plus"></i> {{ _('Register Player') }}
</button>
</form>
</div>
@@ -145,13 +151,13 @@
<table class="table">
<thead>
<tr>
<th>Player</th>
<th>Status</th>
<th>{{ _('Player') }}</th>
<th>{{ _('Status') }}</th>
{% if current_user.can_evaluate() %}
<th>Evaluation</th>
<th>{{ _('Evaluation') }}</th>
{% endif %}
{% if can_edit %}
<th>Actions</th>
<th>{{ _('Actions') }}</th>
{% endif %}
</tr>
</thead>
@@ -169,10 +175,10 @@
{% if can_edit %}
<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 name="status" data-submit-on-change 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 %}
@@ -191,7 +197,7 @@
{% if can_edit %}
<td class="eval-actions">
{% if current_user.can_evaluate() %}
<a href="{{ url_for('evaluations.evaluate_player', tryout_id=tryout.id, player_id=p.id) }}" class="btn btn-sm btn-primary" title="Evaluate player">
<a href="{{ url_for('evaluations.evaluate_player', tryout_id=tryout.id, player_id=p.id) }}" class="btn btn-sm btn-primary" title="{{ _('Evaluate player') }}">
<i class="fas fa-edit"></i> {% if player_eval_status.get(p.id) %}Edit{% else %}Evaluate{% endif %}
</a>
<a href="{{ url_for('users.add_note_from_tryout', tryout_id=tryout.id) }}?player_id={{ p.id }}" class="btn btn-sm btn-outline" title="Add note for {{ p.username }}">
@@ -199,10 +205,10 @@
</a>
{% endif %}
{% if not tryout.is_ended %}
<form method="POST" action="{{ url_for('tryouts.remove_player', tryout_id=tryout.id, player_id=p.id) }}" class="inline-form" onsubmit="return confirm('Remove {{ p.username }} from this tryout? This will also remove them from all teams and matches within this tryout.');">
<form method="POST" action="{{ url_for('tryouts.remove_player', tryout_id=tryout.id, player_id=p.id) }}" class="inline-form" data-confirm="{{ _('Remove %(name)s from this tryout? They will also be removed from every team and match within it.', name=p.username) }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<button type="submit" class="btn btn-sm btn-danger" title="Remove player from tryout">
<i class="fas fa-user-minus"></i> Remove
<button type="submit" class="btn btn-sm btn-danger" title="{{ _('Remove player from tryout') }}">
<i class="fas fa-user-minus"></i> {{ _('Remove') }}
</button>
</form>
{% endif %}
@@ -211,9 +217,7 @@
</tr>
{% else %}
<tr>
<td colspan="{% if can_edit %}{% if current_user.can_evaluate() %}5{% else %}4{% endif %}{% else %}{% if current_user.can_evaluate() %}4{% else %}2{% endif %}{% endif %}" class="text-center">
No players registered yet.
</td>
<td colspan="{% if can_edit %}{% if current_user.can_evaluate() %}5{% else %}4{% endif %}{% else %}{% if current_user.can_evaluate() %}4{% else %}2{% endif %}{% endif %}" class="text-center">{{ _('No players registered yet.') }}</td>
</tr>
{% endfor %}
</tbody>
@@ -224,10 +228,10 @@
<div class="card">
<div class="card-header">
<h3><i class="fas fa-users-cog"></i> Teams</h3>
<h3><i class="fas fa-users-cog"></i> {{ _('Teams') }}</h3>
{% if can_edit and not tryout.is_ended %}
<button class="btn btn-sm btn-primary" onclick="showCreateTeam()">
<i class="fas fa-plus"></i> New Team
<button class="btn btn-sm btn-primary" data-action="show-create-team">
<i class="fas fa-plus"></i> {{ _('New Team') }}
</button>
{% endif %}
</div>
@@ -236,9 +240,9 @@
<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>
<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" data-action="hide-create-team">{{ _('Cancel') }}</button>
</form>
</div>
{% endif %}
@@ -256,7 +260,7 @@
{% endif %}
</li>
{% else %}
<li class="text-muted">No players assigned yet.</li>
<li class="text-muted">{{ _('No players assigned yet.') }}</li>
{% endfor %}
</ul>
{% if can_edit and not tryout.is_ended and registered_players %}
@@ -264,7 +268,7 @@
<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>
<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.username }}</option>
@@ -273,20 +277,20 @@
</select>
{% if positions %}
<select name="position" class="form-select mx-2">
<option value="">-- Select Position --</option>
<option value="">{{ _('-- Select Position --') }}</option>
{% for pos in positions %}
<option value="{{ pos }}">{{ pos }}</option>
{% endfor %}
</select>
{% else %}
<input type="text" name="position" placeholder="Position" class="form-input mx-2">
<input type="text" name="position" placeholder="{{ _('Position') }}" class="form-input mx-2">
{% endif %}
<button type="submit" class="btn btn-sm btn-primary">Add</button>
<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>
<p class="text-muted text-center">{{ _('No teams created yet.') }}</p>
{% endfor %}
</div>
</div>
@@ -295,14 +299,14 @@
{% if can_view_calendar %}
<div class="card tryout-schedule-card mb-4">
<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 and not tryout.is_ended %}
<div class="card-actions">
<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 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>
</div>
{% endif %}
@@ -314,15 +318,15 @@
<table class="table">
<thead>
<tr>
<th>Match</th>
<th>Type</th>
<th>Date</th>
<th>Participants</th>
<th>Time</th>
<th>Presence</th>
<th>Status</th>
<th>{{ _('Match') }}</th>
<th>{{ _('Type') }}</th>
<th>{{ _('Date') }}</th>
<th>{{ _('Participants') }}</th>
<th>{{ _('Time') }}</th>
<th>{{ _('Presence') }}</th>
<th>{{ _('Status') }}</th>
{% if can_edit %}
<th>Actions</th>
<th>{{ _('Actions') }}</th>
{% endif %}
</tr>
</thead>
@@ -368,7 +372,7 @@
{% for pp in item.player_presence %}
<button class="btn btn-xs presence-toggle-btn {% if pp.attendance_confirmed %}presence-confirmed-btn{% else %}presence-pending-btn{% endif %}"
title="{{ pp.player_name }}"
onclick="toggleTryoutPresence({{ m.id }}, {{ pp.participant_id }}, this)">
data-action="toggle-tryout-presence" data-match-id="{{ m.id }}" data-participant-id="{{ pp.participant_id }}">
{{ pp.player_name[:2] | upper }} {% if pp.attendance_confirmed %}✅{% else %}⏳{% endif %}
</button>
{% endfor %}
@@ -380,8 +384,8 @@
{% if pp.player_id == current_user.id %}
<div class="presence-players" style="margin-top:6px;">
<button class="btn btn-xs presence-toggle-btn {% if pp.attendance_confirmed %}presence-confirmed-btn{% else %}presence-pending-btn{% endif %}"
title="Toggle your attendance"
onclick="toggleTryoutPresence({{ m.id }}, {{ pp.participant_id }}, this)">
title="{{ _('Toggle your attendance') }}"
data-action="toggle-tryout-presence" data-match-id="{{ m.id }}" data-participant-id="{{ pp.participant_id }}">
Me {% if pp.attendance_confirmed %}✅{% else %}⏳{% endif %}
</button>
</div>
@@ -452,11 +456,11 @@
<td>
{% if not tryout.is_ended %}
<a href="{{ url_for('matches.edit_match', match_id=m.id) }}" class="btn btn-sm btn-outline">
<i class="fas fa-edit"></i> Edit
<i class="fas fa-edit"></i> {{ _('Edit') }}
</a>
{% endif %}
<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 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 %}
@@ -474,7 +478,7 @@
<!-- 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>
<p class="text-muted">{{ _('No matches scheduled yet. Check back later!') }}</p>
</div>
{% endif %}
</div>
@@ -484,26 +488,26 @@
{% if current_user.can_evaluate() %}
<div class="card">
<div class="card-header">
<h3><i class="fas fa-star"></i> Evaluation Summary</h3>
<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>Mecanics</th>
<th>Cohesion</th>
<th>Communication</th>
<th>Gamesense</th>
<th>Versatility</th>
<th>Discipline</th>
<th>Analysis</th>
<th>Sport Ethics</th>
<th>Mental</th>
<th>Overall</th>
<th>Recommendation</th>
<th>{{ _('Player') }}</th>
<th>{{ _('Evaluator') }}</th>
<th>{{ _('Mecanics') }}</th>
<th>{{ _('Cohesion') }}</th>
<th>{{ _('Communication') }}</th>
<th>{{ _('Gamesense') }}</th>
<th>{{ _('Versatility') }}</th>
<th>{{ _('Discipline') }}</th>
<th>{{ _('Analysis') }}</th>
<th>{{ _('Sport Ethics') }}</th>
<th>{{ _('Mental') }}</th>
<th>{{ _('Overall') }}</th>
<th>{{ _('Recommendation') }}</th>
</tr>
</thead>
<tbody>
@@ -525,7 +529,7 @@
</tr>
{% else %}
<tr>
<td colspan="13" class="text-center">No evaluations yet.</td>
<td colspan="13" class="text-center">{{ _('No evaluations yet.') }}</td>
</tr>
{% endfor %}
</tbody>
@@ -536,6 +540,9 @@
{% endif %}
</div>
{% endblock %}
{% block scripts %}
<style>
.presence-toggle-btn {
padding: 2px 7px;
@@ -562,7 +569,7 @@
box-shadow: 0 1px 3px rgba(0,0,0,0.12);
}
</style>
<script>
<script nonce="{{ csp_nonce }}">
function showCreateTeam() { document.getElementById('createTeamForm').classList.remove('hidden'); }
function hideCreateTeam() { document.getElementById('createTeamForm').classList.add('hidden'); }
@@ -614,5 +621,18 @@ document.addEventListener('DOMContentLoaded', function() {
miniCalendar.render();
}
});
// Behaviours declared in the markup, dispatched by the delegated listener
// in main.js. Inline onclick attributes cannot be authorised by a CSP nonce.
registerActions({
'show-create-team': showCreateTeam,
'hide-create-team': hideCreateTeam,
'toggle-tryout-presence': function (element) {
toggleTryoutPresence(
element.getAttribute('data-match-id'),
element.getAttribute('data-participant-id'),
element);
},
});
</script>
{% endblock %}
+9 -9
View File
@@ -1,7 +1,7 @@
{% extends "layouts/base.html" %}
{% block title %}{{ profile_user.username }} - TryoutPro{% endblock %}
{% block title %}{{ profile_user.username }} - UdeS team manager{% endblock %}
{% block page_title %}{{ profile_user.username }}{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / <a href="#" onclick="history.back()">Back</a> / {{ profile_user.username }}</span>{% endblock %}
{% block breadcrumb %}<span class="breadcrumb">Home / <a href="#" data-action="history-back">Back</a> / {{ profile_user.username }}</span>{% endblock %}
{% block content %}
<div class="card mb-4">
@@ -36,7 +36,7 @@
{% endif %}
{% if profile_user.league_os_profile %}
<div class="detail-item">
<span class="detail-label"><i class="fas fa-link"></i> League OS</span>
<span class="detail-label"><i class="fas fa-link"></i> {{ _('League OS') }}</span>
<span class="detail-value">
<a href="{{ profile_user.league_os_profile }}" target="_blank" rel="noopener noreferrer" class="trn-link">
<i class="fas fa-external-link-alt"></i> {{ profile_user.league_os_profile }}
@@ -49,15 +49,15 @@
{% set gamertags = profile_user.gamertags %}
{% if gamertags %}
<hr class="my-4">
<h4 class="mb-3"><i class="fas fa-gamepad"></i> Gamertags</h4>
<h4 class="mb-3"><i class="fas fa-gamepad"></i> {{ _('Gamertags') }}</h4>
<div class="table-container">
<table class="table">
<thead>
<tr>
<th>Game</th>
<th>Gamertag</th>
<th>Platform</th>
<th>Profile</th>
<th>{{ _('Game') }}</th>
<th>{{ _('Gamertag') }}</th>
<th>{{ _('Platform') }}</th>
<th>{{ _('Profile') }}</th>
</tr>
</thead>
<tbody>
@@ -69,7 +69,7 @@
<td>
{% if gt.get_trn_url() %}
<a href="{{ gt.get_trn_url() }}" target="_blank" class="btn btn-sm btn-outline trn-link">
<i class="fas fa-external-link-alt"></i> View Profile
<i class="fas fa-external-link-alt"></i> {{ _('View Profile') }}
</a>
{% else %}
<span class="text-muted"></span>