240 lines
9.0 KiB
HTML
240 lines
9.0 KiB
HTML
{% extends "layouts/base.html" %}
|
|
{% 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;" data-navigate="/team-matches/{value}/create">
|
|
<option value="">{{ _('+ Schedule Match') }}</option>
|
|
{% for t in teams %}
|
|
<option value="{{ t.id }}">{{ t.name }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
{% endif %}
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
{% if match_data %}
|
|
<div class="table-container">
|
|
<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>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for item in match_data %}
|
|
{% set m = item.match %}
|
|
<tr>
|
|
<td class="cell-title">{{ m.title }}</td>
|
|
<td>
|
|
<span class="badge badge-info">{{ m.org_team.name }}</span>
|
|
</td>
|
|
<td>
|
|
{% if m.opponent %}
|
|
{{ m.opponent }}
|
|
{% else %}
|
|
<span class="badge badge-info">Practice</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>{{ m.date.strftime('%m/%d/%Y') }}</td>
|
|
<td>
|
|
{% if m.start_time and m.end_time %}
|
|
{{ m.start_time.strftime('%H:%M') }} - {{ m.end_time.strftime('%H:%M') }}
|
|
{% else %}
|
|
TBD
|
|
{% endif %}
|
|
</td>
|
|
<td>{{ m.location or '—' }}</td>
|
|
<td>
|
|
{% if item.total_count > 0 %}
|
|
<div class="presence-bar" title="{{ item.confirmed_count }} of {{ item.total_count }} confirmed">
|
|
<div class="presence-progress">
|
|
{% set pct = (item.confirmed_count / item.total_count * 100) | int %}
|
|
<div class="presence-fill" style="width: {{ pct }}%;"></div>
|
|
</div>
|
|
<span class="presence-text">
|
|
{% if item.confirmed_count == item.total_count and item.total_count > 0 %}
|
|
✅ {{ item.confirmed_count }}/{{ item.total_count }}
|
|
{% elif item.confirmed_count > 0 %}
|
|
⏳ {{ item.confirmed_count }}/{{ item.total_count }}
|
|
{% else %}
|
|
❌ 0/{{ item.total_count }}
|
|
{% endif %}
|
|
</span>
|
|
</div>
|
|
<!-- Player presence details -->
|
|
<div class="presence-players">
|
|
{% for p in item.participants %}
|
|
<span class="presence-player-tag {% if p.is_confirmed %}confirmed{% else %}pending{% endif %}"
|
|
title="{{ p.player.username }}{% if p.is_confirmed %} - Confirmed{% else %} - Pending{% endif %}">
|
|
{{ p.player.username[:2] | upper }} {{ p.player.username }}
|
|
{% if p.is_confirmed %}✅{% else %}⏳{% endif %}
|
|
</span>
|
|
{% endfor %}
|
|
</div>
|
|
{% else %}
|
|
<span class="text-muted">—</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>
|
|
<span class="badge badge-{{ m.status }}">{{ m.status }}</span>
|
|
</td>
|
|
<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') }}">
|
|
<i class="fas fa-edit"></i>
|
|
</a>
|
|
<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') }}">
|
|
<i class="fas fa-trash"></i>
|
|
</button>
|
|
</form>
|
|
{% endif %}
|
|
<!-- Toggle presence for each participant if user is player -->
|
|
{% if current_user.role == 'player' %}
|
|
{% for p in item.participants %}
|
|
{% if p.player.id == current_user.id %}
|
|
<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 }}"
|
|
data-action="toggle-presence">
|
|
{% if p.is_confirmed %}✅ Confirmed{% else %}Confirm{% endif %}
|
|
</button>
|
|
{% endif %}
|
|
{% endfor %}
|
|
{% endif %}
|
|
</td>
|
|
</tr>
|
|
{% 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>
|
|
{% if teams %}
|
|
<div class="mt-3">
|
|
<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 %}
|
|
</select>
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endif %}
|
|
|
|
<script nonce="{{ csp_nonce }}">
|
|
function togglePresence(btn) {
|
|
var matchId = btn.getAttribute('data-match-id');
|
|
var participantId = btn.getAttribute('data-participant-id');
|
|
|
|
fetch('/team-matches/' + matchId + '/toggle-presence/' + participantId, {
|
|
method: 'POST',
|
|
headers: {
|
|
'X-CSRFToken': '{{ csrf_token() }}',
|
|
'Content-Type': 'application/json'
|
|
}
|
|
})
|
|
.then(function(response) { return response.json(); })
|
|
.then(function(data) {
|
|
if (data.is_confirmed) {
|
|
btn.classList.add('btn-success');
|
|
btn.classList.remove('btn-outline');
|
|
btn.innerHTML = '✅ Confirmed';
|
|
} else {
|
|
btn.classList.remove('btn-success');
|
|
btn.classList.add('btn-outline');
|
|
btn.innerHTML = 'Confirm';
|
|
}
|
|
// Reload to update presence bar
|
|
location.reload();
|
|
})
|
|
.catch(function(error) {
|
|
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>
|
|
.presence-bar {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
margin-bottom: 4px;
|
|
}
|
|
.presence-progress {
|
|
width: 60px;
|
|
height: 6px;
|
|
background: #e5e7eb;
|
|
border-radius: 3px;
|
|
overflow: hidden;
|
|
}
|
|
.presence-fill {
|
|
height: 100%;
|
|
background: #10b981;
|
|
border-radius: 3px;
|
|
}
|
|
.presence-text {
|
|
font-size: 0.8rem;
|
|
font-weight: 600;
|
|
}
|
|
.presence-players {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 3px;
|
|
margin-top: 3px;
|
|
}
|
|
.presence-player-tag {
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 2px;
|
|
padding: 1px 5px;
|
|
border-radius: 10px;
|
|
font-size: 0.7rem;
|
|
font-weight: 600;
|
|
}
|
|
.presence-player-tag.confirmed {
|
|
background: #d1fae5;
|
|
color: #065f46;
|
|
}
|
|
.presence-player-tag.pending {
|
|
background: #fef3c7;
|
|
color: #92400e;
|
|
}
|
|
.presence-toggle-btn {
|
|
margin-left: 4px;
|
|
}
|
|
</style>
|
|
{% endblock %}
|