Merge branch 'dev' of https://git.immortal.host/clubesportsudes/team-tryouts into dev
This commit is contained in:
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
@@ -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
@@ -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()">×</button>
|
||||
<h3><i class="fas fa-plus-circle"></i> {{ _('Create New Event') }}</h3>
|
||||
<button class="modal-close" data-action="hide-create-event">×</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()">×</button>
|
||||
<h3 id="modalTitle">{{ _('Event Details') }}</h3>
|
||||
<button class="modal-close" data-action="hide-event-modal">×</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 %}
|
||||
|
||||
@@ -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()">×</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 %}
|
||||
|
||||
@@ -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()">×</button>
|
||||
<h3>{{ _('Upload Signed Contract') }}</h3>
|
||||
<button class="modal-close" data-action="hide-upload-signed">×</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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 = {'&': '&', '<': '<', '>': '>', '"': '"', "'": '''};
|
||||
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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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()">×</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">×</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
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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">×</button>
|
||||
<button type="submit" class="btn-icon-sm" title="{{ _('Remove Coach') }}">×</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">×</button>
|
||||
<button type="submit" class="btn-icon-sm" title="{{ _('Remove Manager') }}">×</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()">×</button>
|
||||
<h3>{{ _('Edit Team') }}</h3>
|
||||
<button class="modal-close" data-action="hide-edit-form">×</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 %}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
|
||||
@@ -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 %}
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user