ajustements des calendriers, ajout de matchs jouer vs joueur

This commit is contained in:
cedrick2711
2026-07-14 14:50:48 -04:00
parent 83ee10ca64
commit 4e7ecf0ab9
13 changed files with 338 additions and 41 deletions
+1
View File
@@ -0,0 +1 @@
*.env
+1
View File
@@ -0,0 +1 @@
### Plateforme centralisée de tryouts
Binary file not shown.
Binary file not shown.
+2
View File
@@ -223,6 +223,8 @@ class MatchParticipant(db.Model):
id = db.Column(db.Integer, primary_key=True)
match_id = db.Column(db.Integer, db.ForeignKey('matches.id'), nullable=False)
player_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)
team_side = db.Column(db.Integer, nullable=True) # 1 for team 1, 2 for team 2 (for player_vs_player matches)
position = db.Column(db.String(50), nullable=True) # Position for this match
added_at = db.Column(db.DateTime, default=datetime.utcnow)
player = db.relationship('User')
Binary file not shown.
Binary file not shown.
+85 -22
View File
@@ -1,7 +1,7 @@
from flask import Blueprint, render_template, redirect, url_for, flash, request, jsonify
from flask_login import login_required, current_user
from extensions import db
from models import User, Tryout, Match, MatchParticipant, Team, TeamMember, OrgTeam
from models import User, Tryout, Match, MatchParticipant, Team, TeamMember, OrgTeam, TryoutRegistration
from datetime import datetime, time
matches_bp = Blueprint('matches', __name__, url_prefix='/matches')
@@ -97,30 +97,52 @@ def api_events_for_tryout(tryout_id):
tryout = Tryout.query.get_or_404(tryout_id)
# Check if user can view this tryout
if not current_user.can_manage_this_tryout(tryout):
# For players, check if they're registered or participating in the match
if current_user.role == 'player':
# Check if player is registered for this tryout
is_registered = TryoutRegistration.query.filter_by(
tryout_id=tryout_id, player_id=current_user.id
).first() is not None
# Check if player is participating in any matches for this tryout
match_participation = MatchParticipant.query.filter(
MatchParticipant.match_id == MatchParticipant.match_id
).join(Match).filter(Match.tryout_id == tryout_id).all()
player_in_match = any(mp.player_id == current_user.id for mp in match_participation)
if not is_registered and not player_in_match:
return jsonify([])
can_view = current_user.can_manage_this_tryout(tryout)
# For players, check if they're registered or participating in a match
is_registered = False
player_in_match = False
if current_user.role == 'player':
is_registered = TryoutRegistration.query.filter_by(
tryout_id=tryout_id, player_id=current_user.id
).first() is not None
# Check if player is participating in any matches for this tryout
player_matches = Match.query.join(MatchParticipant).filter(
MatchParticipant.player_id == current_user.id,
Match.tryout_id == tryout_id
).all()
player_in_match = len(player_matches) > 0
# Non-participating players cannot see the calendar
if not can_view and not is_registered and not player_in_match:
return jsonify([])
events = []
# Add tryout date as an event (read-only, for context)
events.append({
'id': f'tryout_{tryout.id}',
'title': f'Tryout: {tryout.title}',
'date': tryout.date.strftime('%Y-%m-%d'),
'type': 'tryout',
'color': '#3b82f6', # Blue for tryouts
'extendedProps': {
'location': tryout.location or 'TBD',
'status': tryout.status,
'description': tryout.description or '',
'tryout_id': tryout.id
}
})
for match in tryout.matches:
match_color = '#10b981' if match.match_type == 'team_vs_team' else '#f59e0b'
# Determine color based on match type
if match.match_type == 'team_vs_team' or match.match_type == 'player_vs_player':
match_color = '#10b981' # Green for team matches
else:
match_color = '#f59e0b' # Orange for scrims
# Build participant string
# Build participant string with proper grouping
participants_str = ''
if match.match_type == 'team_vs_team':
teams = []
@@ -129,10 +151,22 @@ def api_events_for_tryout(tryout_id):
if match.team2:
teams.append(match.team2.name)
participants_str = f"{' vs '.join(teams)}"
elif match.match_type == 'player_vs_player':
# Get players grouped by team side
team1_players = [p.player.full_name for p in match.participants.filter_by(team_side=1).all()]
team2_players = [p.player.full_name for p in match.participants.filter_by(team_side=2).all()]
if team1_players and team2_players:
participants_str = f"{', '.join(team1_players)} vs {', '.join(team2_players)}"
else:
participants_str = 'TBD vs TBD'
else:
player_names = [p.player.full_name for p in match.participants.all()]
participants_str = ', '.join(player_names) if player_names else 'No players'
# Include time for calendar display
start_time_str = match.start_time.strftime('%H:%M') if match.start_time else None
end_time_str = match.end_time.strftime('%H:%M') if match.end_time else None
events.append({
'id': f'match_{match.id}',
'title': match.title + ' (' + participants_str + ')',
@@ -144,7 +178,10 @@ def api_events_for_tryout(tryout_id):
'status': match.status,
'match_type': match.match_type,
'tryout_id': tryout.id,
'match_id': match.id
'match_id': match.id,
'participants': participants_str,
'start_time': start_time_str,
'end_time': end_time_str
}
})
@@ -245,6 +282,17 @@ def create_match(tryout_id):
match.team1_id = int(team1_id) if team1_id else None
match.team2_id = int(team2_id) if team2_id else None
# Handle player vs player matches
elif match_type == 'player_vs_player':
team1_player_ids = request.form.getlist('team1_player_ids')
team2_player_ids = request.form.getlist('team2_player_ids')
for pid in team1_player_ids:
participant = MatchParticipant(match_id=match.id, player_id=int(pid), team_side=1)
db.session.add(participant)
for pid in team2_player_ids:
participant = MatchParticipant(match_id=match.id, player_id=int(pid), team_side=2)
db.session.add(participant)
# Handle player scrim matches
elif match_type == 'player_scrim':
player_ids = request.form.getlist('player_ids')
@@ -273,6 +321,9 @@ def edit_match(match_id):
teams = Team.query.filter_by(tryout_id=tryout.id).all()
all_players = User.query.filter_by(role='player').order_by(User.full_name).all()
current_player_ids = [p.player_id for p in match.participants.all()]
# Get players grouped by team side for player_vs_player matches
team1_player_ids = [p.player_id for p in match.participants.filter_by(team_side=1).all()]
team2_player_ids = [p.player_id for p in match.participants.filter_by(team_side=2).all()]
if request.method == 'POST':
match.title = request.form.get('title')
@@ -308,6 +359,18 @@ def edit_match(match_id):
match.team1_id = int(team1_id) if team1_id else None
match.team2_id = int(team2_id) if team2_id else None
# Handle player vs player matches - update participants
elif match.match_type == 'player_vs_player':
MatchParticipant.query.filter_by(match_id=match.id).delete()
team1_player_ids = request.form.getlist('team1_player_ids')
team2_player_ids = request.form.getlist('team2_player_ids')
for pid in team1_player_ids:
participant = MatchParticipant(match_id=match.id, player_id=int(pid), team_side=1)
db.session.add(participant)
for pid in team2_player_ids:
participant = MatchParticipant(match_id=match.id, player_id=int(pid), team_side=2)
db.session.add(participant)
# Handle player scrim matches - update participants
elif match.match_type == 'player_scrim':
MatchParticipant.query.filter_by(match_id=match.id).delete()
@@ -320,7 +383,7 @@ def edit_match(match_id):
flash('Match updated successfully!', 'success')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout.id))
return render_template('pages/edit_match.html', match=match, tryout=tryout, teams=teams, all_players=all_players, current_player_ids=current_player_ids)
return render_template('pages/edit_match.html', match=match, tryout=tryout, teams=teams, all_players=all_players, current_player_ids=current_player_ids, team1_player_ids=team1_player_ids, team2_player_ids=team2_player_ids)
@matches_bp.route('/<int:match_id>/delete', methods=['POST'])
+27 -3
View File
@@ -144,10 +144,22 @@ def view_tryout(tryout_id):
# Determine if current user can edit this tryout
can_edit = current_user.can_manage_this_tryout(tryout)
# Determine if current user can view the calendar (managers/coaches can always see it)
# Players need to be registered or participating in a match
can_view_calendar = can_edit
if current_user.role == 'player':
# Check if player is participating in any matches for this tryout
player_in_match = MatchParticipant.query.join(Match).filter(
MatchParticipant.player_id == current_user.id,
Match.tryout_id == tryout_id
).first() is not None
can_view_calendar = is_registered or player_in_match
# Get all players (for manager registration dropdown)
all_players = User.query.filter_by(role='player').order_by(User.full_name).all()
# Get matches for this tryout with participant info
matches = Match.query.filter_by(tryout_id=tryout_id).order_by(Match.date).all()
match_data = []
@@ -155,7 +167,18 @@ def view_tryout(tryout_id):
if match.match_type == 'team_vs_team':
participants = {
'team1': match.team1.name if match.team1 else 'TBD',
'team2': match.team2.name if match.team2 else 'TBD'
'team2': match.team2.name if match.team2 else 'TBD',
'team1_players': [{'name': m.player.full_name, 'position': m.position} for m in match.team1.members.all()] if match.team1 else [],
'team2_players': [{'name': m.player.full_name, 'position': m.position} for m in match.team2.members.all()] if match.team2 else []
}
elif match.match_type == 'player_vs_player':
team1_players = [p.player.full_name for p in match.participants.filter_by(team_side=1).all()]
team2_players = [p.player.full_name for p in match.participants.filter_by(team_side=2).all()]
participants = {
'team1': ', '.join(team1_players) if team1_players else 'TBD',
'team2': ', '.join(team2_players) if team2_players else 'TBD',
'team1_player_names': team1_players,
'team2_player_names': team2_players
}
else:
participants = [p.player.full_name for p in match.participants.all()]
@@ -173,6 +196,7 @@ def view_tryout(tryout_id):
registrations=registrations,
team_data=team_data,
can_edit=can_edit,
can_view_calendar=can_view_calendar,
all_players=all_players,
matches=matches,
match_data=match_data,
+41
View File
@@ -949,6 +949,46 @@ a:hover { color: var(--primary-dark); }
gap: 4px;
}
/* Team Roster Display */
.team-roster-display {
font-size: 0.85rem;
color: var(--gray-600);
}
.team-roster-display small {
display: block;
margin-top: 2px;
}
.team-rosters {
display: flex;
gap: 20px;
flex-wrap: wrap;
}
.team-roster {
flex: 1;
min-width: 200px;
}
.team-roster h5 {
font-size: 0.9rem;
margin-bottom: 8px;
color: var(--gray-700);
}
.team-members-list {
list-style: none;
padding: 0;
margin: 0;
font-size: 0.85rem;
}
.team-members-list li {
padding: 4px 0;
color: var(--gray-600);
}
@media (max-width: 768px) {
.sidebar {
width: var(--sidebar-collapsed);
@@ -970,4 +1010,5 @@ a:hover { color: var(--primary-dark); }
.content { padding: 16px; }
.top-bar { padding: 12px 16px; }
.flash-messages { padding: 0 16px; }
.team-rosters { flex-direction: column; }
}
+61 -8
View File
@@ -12,10 +12,11 @@
<form method="POST" action="{{ url_for('matches.create_match', tryout_id=tryout.id) }}" class="form">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
<div class="form-group">
<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>
</select>
</div>
@@ -79,10 +80,41 @@
</div>
</div>
<!-- Player vs Player Selection -->
<div id="player-vs-player-section" class="hidden">
<hr class="section-divider">
<h4 class="section-title"><i class="fas fa-user-friends"></i> Select Players</h4>
<div class="form-row">
<div class="form-group">
<label>Team 1 Players</label>
<div class="checkbox-grid">
{% for player in all_players %}
<label class="checkbox-label">
<input type="checkbox" name="team1_player_ids" value="{{ player.id }}" onchange="handleTeamSelection(this, 1)">
{{ player.full_name }}
</label>
{% endfor %}
</div>
</div>
<div class="form-group">
<label>Team 2 Players</label>
<div class="checkbox-grid">
{% for player in all_players %}
<label class="checkbox-label">
<input type="checkbox" name="team2_player_ids" value="{{ player.id }}" onchange="handleTeamSelection(this, 2)">
{{ player.full_name }}
</label>
{% endfor %}
</div>
</div>
</div>
</div>
<!-- Player Scrim Selection -->
<div id="player-scrim-section" class="hidden">
<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-users"></i> Select Players</h4>
<div class="form-group">
<label>Players</label>
@@ -115,20 +147,41 @@
function toggleMatchType() {
var matchType = document.getElementById('match_type').value;
var teamSection = document.getElementById('team-vs-team-section');
var playerSection = document.getElementById('player-scrim-section');
var playerVsPlayerSection = document.getElementById('player-vs-player-section');
var scrimSection = document.getElementById('player-scrim-section');
// Hide all sections first
teamSection.classList.add('hidden');
playerVsPlayerSection.classList.add('hidden');
scrimSection.classList.add('hidden');
// Show relevant section
if (matchType === 'team_vs_team') {
teamSection.classList.remove('hidden');
playerSection.classList.add('hidden');
} else {
teamSection.classList.add('hidden');
playerSection.classList.remove('hidden');
} else if (matchType === 'player_vs_player') {
playerVsPlayerSection.classList.remove('hidden');
} else if (matchType === 'player_scrim') {
scrimSection.classList.remove('hidden');
}
}
function handleTeamSelection(checkbox, teamSide) {
// When selecting from one team, deselect from the other team
var otherSide = teamSide === 1 ? 2 : 1;
var otherCheckboxes = document.querySelectorAll('input[name="team' + otherSide + '_player_ids"]');
var playerId = checkbox.value;
// Find the corresponding checkbox in the other team and uncheck it
otherCheckboxes.forEach(function(other) {
if (other.value === playerId) {
other.checked = false;
}
});
}
// Initialize on page load
document.addEventListener('DOMContentLoaded', function() {
toggleMatchType();
});
</script>
{% endblock %}
{% endblock %}
+81 -2
View File
@@ -53,6 +53,7 @@
<textarea name="description" id="description" class="form-textarea" placeholder="Optional notes about this match">{{ match.description or '' }}</textarea>
</div>
<!-- Team vs Team Selection -->
{% if match.match_type == 'team_vs_team' %}
<hr class="section-divider">
<h4 class="section-title"><i class="fas fa-users"></i> Teams</h4>
@@ -77,9 +78,69 @@
</select>
</div>
</div>
<!-- Show team rosters -->
<div class="team-rosters mt-3">
{% if match.team1 %}
<div class="team-roster">
<h5>{{ match.team1.name }} Players</h5>
<ul class="team-members-list">
{% for member in match.team1.members %}
<li>{{ member.player.full_name }}{% if member.position %} <span class="position-tag">{{ member.position }}</span>{% endif %}</li>
{% else %}
<li class="text-muted">No players assigned</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% if match.team2 %}
<div class="team-roster">
<h5>{{ match.team2.name }} Players</h5>
<ul class="team-members-list">
{% for member in match.team2.members %}
<li>{{ member.player.full_name }}{% if member.position %} <span class="position-tag">{{ member.position }}</span>{% endif %}</li>
{% else %}
<li class="text-muted">No players assigned</li>
{% endfor %}
</ul>
</div>
{% endif %}
</div>
<!-- Player vs Player Selection -->
{% elif match.match_type == 'player_vs_player' %}
<hr class="section-divider">
<h4 class="section-title"><i class="fas fa-user-friends"></i> Select Players</h4>
<div class="form-row">
<div class="form-group">
<label>Team 1 Players</label>
<div class="checkbox-grid">
{% for player in all_players %}
<label class="checkbox-label">
<input type="checkbox" name="team1_player_ids" value="{{ player.id }}" {% if player.id in team1_player_ids %}checked{% endif %} onchange="handleTeamSelection(this, 1)">
{{ player.full_name }}
</label>
{% endfor %}
</div>
</div>
<div class="form-group">
<label>Team 2 Players</label>
<div class="checkbox-grid">
{% for player in all_players %}
<label class="checkbox-label">
<input type="checkbox" name="team2_player_ids" value="{{ player.id }}" {% if player.id in team2_player_ids %}checked{% endif %} onchange="handleTeamSelection(this, 2)">
{{ player.full_name }}
</label>
{% endfor %}
</div>
</div>
</div>
<!-- Player Scrim Selection -->
{% else %}
<hr class="section-divider">
<h4 class="section-title"><i class="fas fa-user-friends"></i> Players</h4>
<h4 class="section-title"><i class="fas fa-users"></i> Select Players</h4>
<div class="form-group">
<label>Players</label>
@@ -105,4 +166,22 @@
</form>
</div>
</div>
{% endblock %}
{% endblock %}
{% block scripts %}
<script>
function handleTeamSelection(checkbox, teamSide) {
// When selecting from one team, deselect from the other team
var otherSide = teamSide === 1 ? 2 : 1;
var otherCheckboxes = document.querySelectorAll('input[name="team' + otherSide + '_player_ids"]');
var playerId = checkbox.value;
// Find the corresponding checkbox in the other team and uncheck it
otherCheckboxes.forEach(function(other) {
if (other.value === playerId) {
other.checked = false;
}
});
}
</script>
{% endblock %}
+39 -6
View File
@@ -231,10 +231,10 @@
</div>
</div>
{% if matches %}
{% if can_view_calendar %}
<div class="card mt-4">
<div class="card-header">
<h3><i class="fas fa-futbol"></i> Scheduled Matches</h3>
<h3><i class="fas fa-futbol"></i> Schedule</h3>
{% if can_edit %}
<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
@@ -242,9 +242,18 @@
{% endif %}
</div>
<div class="card-body">
{% if matches %}
<!-- Mini Calendar -->
<div id="mini-calendar" style="min-height: 300px;"></div>
{% else %}
<!-- No matches yet - show message -->
<div class="text-center py-4">
<i class="fas fa-calendar-alt fa-3x text-muted mb-3"></i>
<p class="text-muted">No matches scheduled yet. Check back later!</p>
</div>
{% endif %}
{% if matches %}
<!-- Matches Table -->
<div class="table-container mt-3">
<table class="table">
@@ -264,19 +273,42 @@
<tbody>
{% for item in match_data %}
{% set m = item.match %}
{% set participants = item.participants %}
<tr>
<td class="cell-title">{{ m.title }}</td>
<td>
<span class="badge badge-{{ 'success' if m.match_type == 'team_vs_team' else 'warning' }}">
{{ 'Team vs Team' if m.match_type == 'team_vs_team' else 'Player Scrim' }}
<span class="badge badge-{{ 'success' if m.match_type in ['team_vs_team', 'player_vs_player'] else 'warning' }}">
{% if m.match_type == 'team_vs_team' %}
Team vs Team
{% elif m.match_type == 'player_vs_player' %}
Player vs Player
{% else %}
Player Scrim
{% endif %}
</span>
</td>
<td>{{ m.date.strftime('%m/%d/%Y') }}</td>
<td>
{% if m.match_type == 'team_vs_team' %}
{{ item.participants.team1 }} vs {{ item.participants.team2 }}
{{ participants.team1 }} vs {{ participants.team2 }}
{% if participants.team1_players or participants.team2_players %}
<div class="team-roster-display mt-1">
{% if participants.team1_players %}
<small><strong>{{ m.team1.name if m.team1 else 'Team 1' }}:</strong>
{% for pl in participants.team1_players %}{{ pl.name }}{% if not loop.last %}, {% endif %}{% endfor %}
</small><br>
{% endif %}
{% if participants.team2_players %}
<small><strong>{{ m.team2.name if m.team2 else 'Team 2' }}:</strong>
{% for pl in participants.team2_players %}{{ pl.name }}{% if not loop.last %}, {% endif %}{% endfor %}
</small>
{% endif %}
</div>
{% endif %}
{% elif m.match_type == 'player_vs_player' %}
{{ participants.team1 }} vs {{ participants.team2 }}
{% else %}
{{ item.participants | join(', ') }}
{{ participants | join(', ') }}
{% endif %}
</td>
<td>
@@ -301,6 +333,7 @@
</tbody>
</table>
</div>
{% endif %}
</div>
</div>
{% endif %}