Correction des présences et correction de plusieurs erreurs mineur de déplacement/parcours de l'utilisateur, harmonisation des processus

This commit is contained in:
cedrick2711
2026-07-28 18:11:02 -04:00
parent b982cd0318
commit a0f18a886c
19 changed files with 250 additions and 116 deletions
+1 -1
View File
@@ -4,7 +4,7 @@
{% 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 ['president', 'manager', 'coach'] %}
{% 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
</a>
+1 -1
View File
@@ -5,7 +5,7 @@
{% block content %}
<div class="dashboard">
{% if user.role == 'president' %}
{% if user.role == 'admin' %}
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon bg-primary">
+1 -1
View File
@@ -21,7 +21,7 @@
</th>
{% endmacro %}
{% if current_user.role == 'president' and player_scores %}
{% if current_user.role == 'admin' and player_scores %}
<div class="stats-grid mb-4">
{% for pid, data in player_scores.items() %}
<div class="stat-card stat-card-sm">
+6 -1
View File
@@ -75,7 +75,12 @@
{% 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>
<p class="text-muted small">Click time slots consecutively to set match duration. Players available in all selected time blocks are shown below.</p>
<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
</button>
</div>
<div id="merged-disponibility-grid" class="merged-disponibility-grid">
<p class="text-muted">Loading...</p>
+4 -5
View File
@@ -77,12 +77,11 @@
<!-- Player presence details -->
<div class="presence-players">
{% for p in item.participants %}
<a href="{{ url_for('users.view_user', user_id=p.player.id) }}" class="presence-player-tag {% if p.is_confirmed %}confirmed{% else %}pending{% endif %}"
title="{{ p.player.username }}{% if p.is_confirmed %} - Confirmed{% else %} - Pending{% endif %}"
style="text-decoration: none;">
<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 %}
</a>
</span>
{% endfor %}
</div>
{% else %}
@@ -93,7 +92,7 @@
<span class="badge badge-{{ m.status }}">{{ m.status }}</span>
</td>
<td class="eval-actions">
{% set can_manage_this = (current_user.role in ['president', '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) %}
{% 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>
+70 -47
View File
@@ -124,36 +124,10 @@
{% endif %}
</div>
</div>
{% if can_manage %}
<div class="staff-add-forms">
<form method="POST" action="{{ url_for('teams.add_coach', team_id=team.id) }}" class="inline-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<select name="coach_id" class="form-select form-select-sm" onchange="if(this.value) this.form.submit()">
<option value="">+ Add Coach</option>
{% for c in coaches %}
{% if c.id not in (team_coaches | map(attribute='id') | list) %}
<option value="{{ c.id }}">{{ c.username }}</option>
{% endif %}
{% endfor %}
</select>
</form>
<form method="POST" action="{{ url_for('teams.add_manager', team_id=team.id) }}" class="inline-form">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<select name="manager_id" class="form-select form-select-sm" onchange="if(this.value) this.form.submit()">
<option value="">+ Add Manager</option>
{% for m in managers %}
{% if m.id not in (team_managers | map(attribute='id') | list) %}
<option value="{{ m.id }}">{{ m.username }}</option>
{% endif %}
{% endfor %}
</select>
</form>
</div>
{% endif %}
<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 ['president', '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)) %}
{% 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>
@@ -286,28 +260,37 @@
<div class="modal-body">
<form id="editTeamForm" method="POST" action="" class="form">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<input type="hidden" name="sync_staff" value="1"/>
<div class="form-group">
<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>
<div class="form-group">
<label for="edit_coach_id">Assigned Coach</label>
<select id="edit_coach_id" name="coach_id" class="form-select">
<option value="">-- No coach assigned --</option>
<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 }}">{{ coach.username }}</option>
<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>
</div>
<!-- Manage Managers -->
<h5 class="mb-2"><i class="fas fa-user-tie"></i> Managers</h5>
<div class="form-group">
<label for="edit_manager_id">Assigned Manager</label>
<select id="edit_manager_id" name="manager_id" class="form-select">
<option value="">-- No manager assigned --</option>
<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 }}">{{ manager.username }}</option>
<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>
</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>
@@ -355,28 +338,68 @@ function toggleStatus(btn) {
});
}
var currentEditTeamId = null;
document.addEventListener('DOMContentLoaded', function() {
document.querySelectorAll('.edit-team-btn').forEach(function(btn) {
btn.addEventListener('click', function() {
var teamId = this.getAttribute('data-team-id');
currentEditTeamId = this.getAttribute('data-team-id');
var teamName = this.getAttribute('data-team-name');
var coachId = this.getAttribute('data-coach-id');
var managerId = this.getAttribute('data-manager-id');
document.getElementById('editTeamForm').action = '/teams/' + teamId + '/edit';
document.getElementById('editTeamForm').action = '/teams/' + currentEditTeamId + '/edit';
document.getElementById('edit_name').value = teamName;
document.getElementById('edit_coach_id').value = coachId || '';
document.getElementById('edit_manager_id').value = managerId || '';
document.getElementById('editTeamModal').classList.remove('hidden');
populateEditSelects(currentEditTeamId);
});
});
});
function showEditForm(teamId, teamName, coachId, managerId) {
document.getElementById('editTeamForm').action = '/teams/' + teamId + '/edit';
document.getElementById('edit_name').value = teamName;
document.getElementById('edit_coach_id').value = coachId || '';
document.getElementById('edit_manager_id').value = managerId || '';
document.getElementById('editTeamModal').classList.remove('hidden');
function getCurrentStaffIds(teamId, type) {
// Get currently assigned coach/manager IDs from the staff bar pills
var ids = [];
var teamCard = document.querySelector('[data-team-id="' + teamId + '"]');
if (!teamCard) return ids;
var staffBar = teamCard.closest('.card').querySelector('.team-staff-bar');
if (!staffBar) return ids;
var groupIndex = type === 'coach' ? 0 : 1;
var group = staffBar.querySelectorAll('.staff-group')[groupIndex];
if (!group) return ids;
group.querySelectorAll('.staff-tag form input[name="coach_id"], .staff-tag form input[name="manager_id"]').forEach(function(input) {
ids.push(input.value);
});
return ids;
}
function populateEditSelects(teamId) {
var coachSelect = document.getElementById('edit-coach-select');
var managerSelect = document.getElementById('edit-manager-select');
var currentCoachIds = getCurrentStaffIds(teamId, 'coach');
var currentManagerIds = getCurrentStaffIds(teamId, 'manager');
// Show all coaches, but pre-select current ones and hide non-assigned
// Actually: show only currently-assigned options (pre-selected)
coachSelect.querySelectorAll('.coach-option').forEach(function(opt) {
var isAssigned = currentCoachIds.includes(opt.value);
opt.selected = isAssigned;
// Always show all options so user can add/remove
opt.style.display = '';
});
managerSelect.querySelectorAll('.manager-option').forEach(function(opt) {
var isAssigned = currentManagerIds.includes(opt.value);
opt.selected = isAssigned;
opt.style.display = '';
});
// Also update text to reflect current count
document.querySelector('#edit-coach-select + .form-text').textContent =
'Currently assigned: ' + currentCoachIds.length + '. Hold Ctrl/Cmd to select multiple.';
document.querySelector('#edit-manager-select + .form-text').textContent =
'Currently assigned: ' + currentManagerIds.length + '. Hold Ctrl/Cmd to select multiple.';
}
function hideEditForm() {
+64
View File
@@ -338,6 +338,18 @@
<span class="badge badge-secondary">⏳ 0/{{ item.total_count }}</span>
{% endif %}
</span>
<!-- Per-player presence toggles -->
{% if can_edit and item.player_presence %}
<div class="presence-players" style="margin-top:6px;">
{% 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)">
{{ pp.player_name[:2] | upper }} {% if pp.attendance_confirmed %}✅{% else %}⏳{% endif %}
</button>
{% endfor %}
</div>
{% endif %}
{% else %}
<span class="text-muted"></span>
{% endif %}
@@ -484,10 +496,62 @@
{% endif %}
</div>
<style>
.presence-toggle-btn {
padding: 2px 7px;
font-size: 0.7rem;
border-radius: 10px;
border: 1px solid #d1d5db;
cursor: pointer;
margin: 2px;
font-weight: 600;
transition: all 0.15s ease;
}
.presence-confirmed-btn {
background: #d1fae5;
color: #065f46;
border-color: #a7f3d0;
}
.presence-pending-btn {
background: #fef3c7;
color: #92400e;
border-color: #fde68a;
}
.presence-toggle-btn:hover {
transform: scale(1.08);
box-shadow: 0 1px 3px rgba(0,0,0,0.12);
}
</style>
<script>
function showCreateTeam() { document.getElementById('createTeamForm').classList.remove('hidden'); }
function hideCreateTeam() { document.getElementById('createTeamForm').classList.add('hidden'); }
function toggleTryoutPresence(matchId, participantId, btn) {
fetch('/matches/' + matchId + '/toggle-presence/' + participantId, {
method: 'POST',
headers: {
'X-CSRFToken': '{{ csrf_token() }}',
'Content-Type': 'application/json'
}
})
.then(function(r) { return r.json(); })
.then(function(data) {
if (data.attendance_confirmed) {
btn.classList.add('presence-confirmed-btn');
btn.classList.remove('presence-pending-btn');
btn.innerHTML = btn.innerHTML.replace('⏳', '✅');
} else {
btn.classList.remove('presence-confirmed-btn');
btn.classList.add('presence-pending-btn');
btn.innerHTML = btn.innerHTML.replace('✅', '⏳');
}
location.reload();
})
.catch(function(err) {
console.error('Error toggling tryout presence:', err);
});
}
// Mini calendar for matches
document.addEventListener('DOMContentLoaded', function() {
var miniCalendarEl = document.getElementById('mini-calendar');