fix probleme avec dispos
This commit is contained in:
@@ -11,6 +11,7 @@ class Tryout(db.Model):
|
|||||||
description = db.Column(db.Text, nullable=True)
|
description = db.Column(db.Text, nullable=True)
|
||||||
game = db.Column(db.String(50), nullable=False)
|
game = db.Column(db.String(50), nullable=False)
|
||||||
date = db.Column(db.Date, nullable=False)
|
date = db.Column(db.Date, nullable=False)
|
||||||
|
end_date = db.Column(db.Date, nullable=True)
|
||||||
location = db.Column(db.String(200), nullable=True)
|
location = db.Column(db.String(200), nullable=True)
|
||||||
status = db.Column(db.String(20), default='upcoming')
|
status = db.Column(db.String(20), default='upcoming')
|
||||||
max_players = db.Column(db.Integer, nullable=True)
|
max_players = db.Column(db.Integer, nullable=True)
|
||||||
@@ -27,3 +28,13 @@ class Tryout(db.Model):
|
|||||||
evaluations = db.relationship('Evaluation', backref='tryout', lazy='dynamic')
|
evaluations = db.relationship('Evaluation', backref='tryout', lazy='dynamic')
|
||||||
teams = db.relationship('Team', backref='tryout', lazy='dynamic')
|
teams = db.relationship('Team', backref='tryout', lazy='dynamic')
|
||||||
target_org_team = db.relationship('OrgTeam', backref='tryouts', foreign_keys=[target_org_team_id])
|
target_org_team = db.relationship('OrgTeam', backref='tryouts', foreign_keys=[target_org_team_id])
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_ended(self):
|
||||||
|
"""Tryout is considered ended after its end_date passes.
|
||||||
|
Falls back to date if end_date is not set."""
|
||||||
|
from datetime import date as date_type
|
||||||
|
today = date_type.today()
|
||||||
|
if self.end_date is not None:
|
||||||
|
return self.end_date < today
|
||||||
|
return self.date < today
|
||||||
|
|||||||
+13
-25
@@ -46,19 +46,6 @@ def api_events():
|
|||||||
tryouts = get_visible_tryouts_for_user()
|
tryouts = get_visible_tryouts_for_user()
|
||||||
|
|
||||||
for tryout in tryouts:
|
for tryout in tryouts:
|
||||||
events.append({
|
|
||||||
'id': f'tryout_{tryout.id}',
|
|
||||||
'title': tryout.title,
|
|
||||||
'date': tryout.date.strftime('%Y-%m-%d'),
|
|
||||||
'type': 'tryout', 'color': '#3b82f6',
|
|
||||||
'extendedProps': {
|
|
||||||
'location': tryout.location or 'TBD',
|
|
||||||
'status': tryout.status,
|
|
||||||
'description': tryout.description or '',
|
|
||||||
'tryout_id': tryout.id,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
for match in tryout.matches:
|
for match in tryout.matches:
|
||||||
match_color = '#10b981' if match.match_type == 'team_vs_team' else '#f59e0b'
|
match_color = '#10b981' if match.match_type == 'team_vs_team' else '#f59e0b'
|
||||||
match_desc = match.description or ''
|
match_desc = match.description or ''
|
||||||
@@ -158,18 +145,7 @@ def api_events_for_tryout(tryout_id):
|
|||||||
if not can_view and not is_registered and not player_in_match:
|
if not can_view and not is_registered and not player_in_match:
|
||||||
return jsonify([])
|
return jsonify([])
|
||||||
|
|
||||||
events = [{
|
events = []
|
||||||
'id': f'tryout_{tryout.id}',
|
|
||||||
'title': f'Tryout: {tryout.title}',
|
|
||||||
'date': tryout.date.strftime('%Y-%m-%d'),
|
|
||||||
'type': 'tryout', 'color': '#3b82f6',
|
|
||||||
'extendedProps': {
|
|
||||||
'location': tryout.location or 'TBD',
|
|
||||||
'status': tryout.status,
|
|
||||||
'description': tryout.description or '',
|
|
||||||
'tryout_id': tryout.id,
|
|
||||||
},
|
|
||||||
}]
|
|
||||||
|
|
||||||
for match in tryout.matches:
|
for match in tryout.matches:
|
||||||
match_color = '#10b981' if match.match_type in ('team_vs_team', 'player_vs_player') else '#f59e0b'
|
match_color = '#10b981' if match.match_type in ('team_vs_team', 'player_vs_player') else '#f59e0b'
|
||||||
@@ -221,6 +197,10 @@ def create_match(tryout_id):
|
|||||||
flash('You do not have permission to schedule matches for this tryout.', 'danger')
|
flash('You do not have permission to schedule matches for this tryout.', 'danger')
|
||||||
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
|
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
|
||||||
|
|
||||||
|
if tryout.is_ended:
|
||||||
|
flash('This tryout has ended. Matches can no longer be created or modified.', 'danger')
|
||||||
|
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
|
||||||
|
|
||||||
teams = Team.query.filter_by(tryout_id=tryout_id).all()
|
teams = Team.query.filter_by(tryout_id=tryout_id).all()
|
||||||
registrations = TryoutRegistration.query.filter_by(tryout_id=tryout_id).all()
|
registrations = TryoutRegistration.query.filter_by(tryout_id=tryout_id).all()
|
||||||
all_players = [User.query.get(r.player_id) for r in registrations if User.query.get(r.player_id)]
|
all_players = [User.query.get(r.player_id) for r in registrations if User.query.get(r.player_id)]
|
||||||
@@ -348,6 +328,10 @@ def edit_match(match_id):
|
|||||||
flash('You do not have permission to edit this match.', 'danger')
|
flash('You do not have permission to edit this match.', 'danger')
|
||||||
return redirect(url_for('matches.calendar'))
|
return redirect(url_for('matches.calendar'))
|
||||||
|
|
||||||
|
if tryout.is_ended:
|
||||||
|
flash('This tryout has ended. Matches can no longer be created or modified.', 'danger')
|
||||||
|
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout.id))
|
||||||
|
|
||||||
teams = Team.query.filter_by(tryout_id=tryout.id).all()
|
teams = Team.query.filter_by(tryout_id=tryout.id).all()
|
||||||
registrations = TryoutRegistration.query.filter_by(tryout_id=tryout.id).all()
|
registrations = TryoutRegistration.query.filter_by(tryout_id=tryout.id).all()
|
||||||
all_players = [User.query.get(r.player_id) for r in registrations if r.player_id]
|
all_players = [User.query.get(r.player_id) for r in registrations if r.player_id]
|
||||||
@@ -503,6 +487,7 @@ def api_manageable_tryouts():
|
|||||||
manageable.append({
|
manageable.append({
|
||||||
'id': t.id, 'title': t.title,
|
'id': t.id, 'title': t.title,
|
||||||
'date': t.date.strftime('%Y-%m-%d'),
|
'date': t.date.strftime('%Y-%m-%d'),
|
||||||
|
'end_date': t.end_date.strftime('%Y-%m-%d') if t.end_date else None,
|
||||||
})
|
})
|
||||||
return jsonify(manageable)
|
return jsonify(manageable)
|
||||||
|
|
||||||
@@ -516,6 +501,9 @@ def delete_match(match_id):
|
|||||||
if not current_user.can_manage_this_tryout(tryout):
|
if not current_user.can_manage_this_tryout(tryout):
|
||||||
flash('You do not have permission to delete this match.', 'danger')
|
flash('You do not have permission to delete this match.', 'danger')
|
||||||
return redirect(url_for('matches.calendar'))
|
return redirect(url_for('matches.calendar'))
|
||||||
|
if tryout.is_ended:
|
||||||
|
flash('This tryout has ended. Matches can no longer be deleted.', 'danger')
|
||||||
|
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout.id))
|
||||||
db.session.delete(match)
|
db.session.delete(match)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash('Match deleted successfully.', 'success')
|
flash('Match deleted successfully.', 'success')
|
||||||
|
|||||||
+75
-2
@@ -51,6 +51,7 @@ def create_tryout():
|
|||||||
description = request.form.get('description')
|
description = request.form.get('description')
|
||||||
game = request.form.get('game')
|
game = request.form.get('game')
|
||||||
date_str = request.form.get('date')
|
date_str = request.form.get('date')
|
||||||
|
end_date_str = request.form.get('end_date')
|
||||||
location = request.form.get('location')
|
location = request.form.get('location')
|
||||||
max_players = request.form.get('max_players')
|
max_players = request.form.get('max_players')
|
||||||
target_org_team_id = request.form.get('target_org_team_id')
|
target_org_team_id = request.form.get('target_org_team_id')
|
||||||
@@ -60,12 +61,26 @@ def create_tryout():
|
|||||||
try:
|
try:
|
||||||
date_obj = datetime.strptime(date_str, '%Y-%m-%d').date()
|
date_obj = datetime.strptime(date_str, '%Y-%m-%d').date()
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
flash('Invalid date format.', 'danger')
|
flash('Invalid start date format.', 'danger')
|
||||||
|
return render_template('pages/tryout_form.html', tryout=None, org_teams=org_teams,
|
||||||
|
managers=managers, coaches=coaches, esport_games=ESPORT_GAMES)
|
||||||
|
|
||||||
|
end_date_obj = None
|
||||||
|
if end_date_str:
|
||||||
|
try:
|
||||||
|
end_date_obj = datetime.strptime(end_date_str, '%Y-%m-%d').date()
|
||||||
|
if end_date_obj < date_obj:
|
||||||
|
flash('End date cannot be before start date.', 'danger')
|
||||||
|
return render_template('pages/tryout_form.html', tryout=None, org_teams=org_teams,
|
||||||
|
managers=managers, coaches=coaches, esport_games=ESPORT_GAMES)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
flash('Invalid end date format.', 'danger')
|
||||||
return render_template('pages/tryout_form.html', tryout=None, org_teams=org_teams,
|
return render_template('pages/tryout_form.html', tryout=None, org_teams=org_teams,
|
||||||
managers=managers, coaches=coaches, esport_games=ESPORT_GAMES)
|
managers=managers, coaches=coaches, esport_games=ESPORT_GAMES)
|
||||||
|
|
||||||
tryout = Tryout(
|
tryout = Tryout(
|
||||||
title=title, description=description, game=game, date=date_obj,
|
title=title, description=description, game=game, date=date_obj,
|
||||||
|
end_date=end_date_obj,
|
||||||
location=location,
|
location=location,
|
||||||
max_players=int(max_players) if max_players else None,
|
max_players=int(max_players) if max_players else None,
|
||||||
created_by=current_user.id, status='upcoming',
|
created_by=current_user.id, status='upcoming',
|
||||||
@@ -92,6 +107,10 @@ def edit_tryout(tryout_id):
|
|||||||
flash('You do not have permission to edit this tryout.', 'danger')
|
flash('You do not have permission to edit this tryout.', 'danger')
|
||||||
return redirect(url_for('tryouts.list_tryouts'))
|
return redirect(url_for('tryouts.list_tryouts'))
|
||||||
|
|
||||||
|
if tryout.is_ended:
|
||||||
|
flash('This tryout has ended and can no longer be modified.', 'danger')
|
||||||
|
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout.id))
|
||||||
|
|
||||||
org_teams = OrgTeam.query.order_by(OrgTeam.name).all()
|
org_teams = OrgTeam.query.order_by(OrgTeam.name).all()
|
||||||
managers = User.query.filter_by(role='manager', is_active_account=True).order_by(User.full_name).all()
|
managers = User.query.filter_by(role='manager', is_active_account=True).order_by(User.full_name).all()
|
||||||
coaches = User.query.filter_by(role='coach', is_active_account=True).order_by(User.full_name).all()
|
coaches = User.query.filter_by(role='coach', is_active_account=True).order_by(User.full_name).all()
|
||||||
@@ -101,6 +120,7 @@ def edit_tryout(tryout_id):
|
|||||||
description = request.form.get('description')
|
description = request.form.get('description')
|
||||||
game = request.form.get('game')
|
game = request.form.get('game')
|
||||||
date_str = request.form.get('date')
|
date_str = request.form.get('date')
|
||||||
|
end_date_str = request.form.get('end_date')
|
||||||
location = request.form.get('location')
|
location = request.form.get('location')
|
||||||
max_players = request.form.get('max_players')
|
max_players = request.form.get('max_players')
|
||||||
target_org_team_id = request.form.get('target_org_team_id')
|
target_org_team_id = request.form.get('target_org_team_id')
|
||||||
@@ -110,7 +130,20 @@ def edit_tryout(tryout_id):
|
|||||||
try:
|
try:
|
||||||
date_obj = datetime.strptime(date_str, '%Y-%m-%d').date()
|
date_obj = datetime.strptime(date_str, '%Y-%m-%d').date()
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
flash('Invalid date format.', 'danger')
|
flash('Invalid start date format.', 'danger')
|
||||||
|
return render_template('pages/tryout_form.html', tryout=tryout, org_teams=org_teams,
|
||||||
|
managers=managers, coaches=coaches, esport_games=ESPORT_GAMES)
|
||||||
|
|
||||||
|
end_date_obj = None
|
||||||
|
if end_date_str:
|
||||||
|
try:
|
||||||
|
end_date_obj = datetime.strptime(end_date_str, '%Y-%m-%d').date()
|
||||||
|
if end_date_obj < date_obj:
|
||||||
|
flash('End date cannot be before start date.', 'danger')
|
||||||
|
return render_template('pages/tryout_form.html', tryout=tryout, org_teams=org_teams,
|
||||||
|
managers=managers, coaches=coaches, esport_games=ESPORT_GAMES)
|
||||||
|
except (ValueError, TypeError):
|
||||||
|
flash('Invalid end date format.', 'danger')
|
||||||
return render_template('pages/tryout_form.html', tryout=tryout, org_teams=org_teams,
|
return render_template('pages/tryout_form.html', tryout=tryout, org_teams=org_teams,
|
||||||
managers=managers, coaches=coaches, esport_games=ESPORT_GAMES)
|
managers=managers, coaches=coaches, esport_games=ESPORT_GAMES)
|
||||||
|
|
||||||
@@ -118,6 +151,7 @@ def edit_tryout(tryout_id):
|
|||||||
tryout.description = description
|
tryout.description = description
|
||||||
tryout.game = game
|
tryout.game = game
|
||||||
tryout.date = date_obj
|
tryout.date = date_obj
|
||||||
|
tryout.end_date = end_date_obj
|
||||||
tryout.location = location
|
tryout.location = location
|
||||||
tryout.max_players = int(max_players) if max_players else None
|
tryout.max_players = int(max_players) if max_players else None
|
||||||
tryout.target_org_team_id = int(target_org_team_id) if target_org_team_id else None
|
tryout.target_org_team_id = int(target_org_team_id) if target_org_team_id else None
|
||||||
@@ -430,3 +464,42 @@ def add_to_team(tryout_id, team_id):
|
|||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash('Player added to team!', 'success')
|
flash('Player added to team!', 'success')
|
||||||
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
|
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
|
||||||
|
|
||||||
|
|
||||||
|
@tryouts_bp.route('/<int:tryout_id>/delete', methods=['POST'])
|
||||||
|
@login_required
|
||||||
|
def delete_tryout(tryout_id):
|
||||||
|
"""Delete a tryout and all associated data (matches, teams, registrations, evaluations)."""
|
||||||
|
tryout = Tryout.query.get_or_404(tryout_id)
|
||||||
|
if not current_user.can_manage_this_tryout(tryout):
|
||||||
|
flash('You do not have permission to delete this tryout.', 'danger')
|
||||||
|
return redirect(url_for('tryouts.list_tryouts'))
|
||||||
|
|
||||||
|
# Delete match participants for all matches in this tryout
|
||||||
|
match_ids = [m.id for m in Match.query.filter_by(tryout_id=tryout_id).all()]
|
||||||
|
if match_ids:
|
||||||
|
MatchParticipant.query.filter(
|
||||||
|
MatchParticipant.match_id.in_(match_ids)
|
||||||
|
).delete(synchronize_session=False)
|
||||||
|
# Delete matches
|
||||||
|
Match.query.filter(Match.id.in_(match_ids)).delete(synchronize_session=False)
|
||||||
|
|
||||||
|
# Delete team members for all teams in this tryout
|
||||||
|
team_ids = [t.id for t in Team.query.filter_by(tryout_id=tryout_id).all()]
|
||||||
|
if team_ids:
|
||||||
|
TeamMember.query.filter(
|
||||||
|
TeamMember.team_id.in_(team_ids)
|
||||||
|
).delete(synchronize_session=False)
|
||||||
|
# Delete teams
|
||||||
|
Team.query.filter(Team.id.in_(team_ids)).delete(synchronize_session=False)
|
||||||
|
|
||||||
|
# Delete registrations
|
||||||
|
TryoutRegistration.query.filter_by(tryout_id=tryout_id).delete()
|
||||||
|
|
||||||
|
# Delete evaluations
|
||||||
|
Evaluation.query.filter_by(tryout_id=tryout_id).delete()
|
||||||
|
|
||||||
|
db.session.delete(tryout)
|
||||||
|
db.session.commit()
|
||||||
|
flash('Tryout deleted successfully.', 'success')
|
||||||
|
return redirect(url_for('tryouts.list_tryouts'))
|
||||||
+9
-1
@@ -258,7 +258,15 @@ def profile():
|
|||||||
contracts = Contract.query.filter_by(
|
contracts = Contract.query.filter_by(
|
||||||
player_id=current_user.id,
|
player_id=current_user.id,
|
||||||
).order_by(Contract.uploaded_at.desc()).all()
|
).order_by(Contract.uploaded_at.desc()).all()
|
||||||
return render_template('pages/profile.html', user=current_user, contracts=contracts)
|
|
||||||
|
existing_availability = None
|
||||||
|
if isinstance(current_user, Coach):
|
||||||
|
existing_availability = CoachAvailability.query.filter_by(
|
||||||
|
coach_id=current_user.id,
|
||||||
|
).all()
|
||||||
|
|
||||||
|
return render_template('pages/profile.html', user=current_user, contracts=contracts,
|
||||||
|
existing_availability=existing_availability)
|
||||||
|
|
||||||
|
|
||||||
@users_bp.route('/profile/edit', methods=['GET', 'POST'])
|
@users_bp.route('/profile/edit', methods=['GET', 'POST'])
|
||||||
|
|||||||
@@ -197,7 +197,6 @@ function goToTeamMatch() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function goToCreateTryout() {
|
function goToCreateTryout() {
|
||||||
// Tryout creation doesn't support pre-filling date easily, just navigate
|
|
||||||
window.location.href = '/tryouts/create';
|
window.location.href = '/tryouts/create';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,8 +207,13 @@ function fetchTryoutOptions() {
|
|||||||
.then(function(data) {
|
.then(function(data) {
|
||||||
var sel = document.getElementById('createTryoutSelect');
|
var sel = document.getElementById('createTryoutSelect');
|
||||||
sel.innerHTML = '<option value="">-- Select a tryout --</option>';
|
sel.innerHTML = '<option value="">-- Select a tryout --</option>';
|
||||||
|
var today = new Date().toISOString().split('T')[0];
|
||||||
data.forEach(function(t) {
|
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.innerHTML += '<option value="' + t.id + '">' + t.title + ' (' + t.date + ')</option>';
|
||||||
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
.catch(function() {});
|
.catch(function() {});
|
||||||
@@ -236,8 +240,8 @@ function showEventModal(event) {
|
|||||||
|
|
||||||
var content = '<div class="detail-grid">';
|
var content = '<div class="detail-grid">';
|
||||||
content += '<div class="detail-item"><span class="detail-label">Type</span><span class="detail-value">';
|
content += '<div class="detail-item"><span class="detail-label">Type</span><span class="detail-value">';
|
||||||
content += '<span class="badge badge-' + (type === 'tryout' ? 'info' : (props.match_type === 'team_vs_team' || props.match_type === 'player_vs_player' ? 'success' : 'warning')) + '">';
|
content += '<span class="badge badge-' + (props.match_type === 'team_vs_team' || props.match_type === 'player_vs_player' ? 'success' : 'warning') + '">';
|
||||||
content += (type === 'tryout' ? 'Tryout' : (props.match_type === 'team_vs_team' ? 'Team Match' : (props.match_type === 'player_vs_player' ? 'Player Match' : 'Player Scrim'))) + '</span>';
|
content += (props.match_type === 'team_vs_team' ? 'Team Match' : (props.match_type === 'player_vs_player' ? 'Player Match' : 'Player Scrim')) + '</span>';
|
||||||
content += '</span></div>';
|
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">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">Date</span><span class="detail-value">' + date + '</span></div>';
|
||||||
@@ -250,7 +254,6 @@ function showEventModal(event) {
|
|||||||
if (type === 'match' && props.participants) {
|
if (type === 'match' && props.participants) {
|
||||||
content += '<div class="detail-item full-width"><span class="detail-label">Teams</span><span class="detail-value">';
|
content += '<div class="detail-item full-width"><span class="detail-label">Teams</span><span class="detail-value">';
|
||||||
if (props.match_type === 'team_vs_team') {
|
if (props.match_type === 'team_vs_team') {
|
||||||
// For team vs team, participants is "Team1 vs Team2"
|
|
||||||
var teams = props.participants.split(' vs ');
|
var teams = props.participants.split(' vs ');
|
||||||
if (teams.length >= 2) {
|
if (teams.length >= 2) {
|
||||||
content += '<div class="match-teams">';
|
content += '<div class="match-teams">';
|
||||||
@@ -262,8 +265,6 @@ function showEventModal(event) {
|
|||||||
content += props.participants;
|
content += props.participants;
|
||||||
}
|
}
|
||||||
} else if (props.match_type === 'player_vs_player') {
|
} else if (props.match_type === 'player_vs_player') {
|
||||||
// For player vs player, we need to parse the participants
|
|
||||||
// The format is "player1, player2 vs player3, player4"
|
|
||||||
var parts = props.participants.split(' vs ');
|
var parts = props.participants.split(' vs ');
|
||||||
if (parts.length >= 2) {
|
if (parts.length >= 2) {
|
||||||
content += '<div class="match-teams">';
|
content += '<div class="match-teams">';
|
||||||
@@ -275,7 +276,6 @@ function showEventModal(event) {
|
|||||||
content += props.participants;
|
content += props.participants;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// For player scrim, just show the list
|
|
||||||
content += props.participants;
|
content += props.participants;
|
||||||
}
|
}
|
||||||
content += '</span></div>';
|
content += '</span></div>';
|
||||||
@@ -286,7 +286,7 @@ function showEventModal(event) {
|
|||||||
}
|
}
|
||||||
content += '</div>';
|
content += '</div>';
|
||||||
|
|
||||||
document.getElementById('modalTitle').textContent = type === 'tryout' ? 'Tryout Details' : 'Match Details';
|
document.getElementById('modalTitle').textContent = 'Match Details';
|
||||||
document.getElementById('modalContent').innerHTML = content;
|
document.getElementById('modalContent').innerHTML = content;
|
||||||
|
|
||||||
// Reset buttons
|
// Reset buttons
|
||||||
@@ -301,11 +301,11 @@ function showEventModal(event) {
|
|||||||
document.getElementById('editMatchBtn').onclick = function() {
|
document.getElementById('editMatchBtn').onclick = function() {
|
||||||
window.location.href = '/matches/' + props.match_id + '/edit';
|
window.location.href = '/matches/' + props.match_id + '/edit';
|
||||||
};
|
};
|
||||||
} else if (type === 'tryout' && canScheduleMatches) {
|
document.getElementById('deleteMatchBtn').style.display = 'inline-flex';
|
||||||
document.getElementById('modalActions').style.display = 'flex';
|
document.getElementById('deleteMatchBtn').onclick = function() {
|
||||||
document.getElementById('viewTryoutBtn').style.display = 'inline-flex';
|
if (confirm('Are you sure you want to delete this match?')) {
|
||||||
document.getElementById('viewTryoutBtn').onclick = function() {
|
deleteCalendarMatch(props.match_id);
|
||||||
window.location.href = '/tryouts/' + props.tryout_id;
|
}
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
document.getElementById('modalActions').style.display = 'none';
|
document.getElementById('modalActions').style.display = 'none';
|
||||||
@@ -329,6 +329,27 @@ function showEventModal(event) {
|
|||||||
document.getElementById('eventModal').classList.remove('hidden');
|
document.getElementById('eventModal').classList.remove('hidden');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function deleteCalendarMatch(matchId) {
|
||||||
|
fetch('/matches/' + matchId + '/delete', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': '{{ csrf_token() }}',
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(function(r) { return r.json().catch(function() { return {}; }); })
|
||||||
|
.then(function() {
|
||||||
|
hideEventModal();
|
||||||
|
if (window.fcCalendar) {
|
||||||
|
window.fcCalendar.refetchEvents();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function(err) {
|
||||||
|
console.error('Error deleting match:', err);
|
||||||
|
alert('Failed to delete match.');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function toggleCalendarPresence(matchId, participantId, btn) {
|
function toggleCalendarPresence(matchId, participantId, btn) {
|
||||||
fetch('/matches/' + matchId + '/toggle-presence/' + participantId, {
|
fetch('/matches/' + matchId + '/toggle-presence/' + participantId, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
|
|||||||
@@ -104,25 +104,6 @@
|
|||||||
<input type="password" id="password" name="password" placeholder="Enter new password">
|
<input type="password" id="password" name="password" placeholder="Enter new password">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if user.role == 'player' %}
|
|
||||||
<hr class="section-divider">
|
|
||||||
<h4 class="section-title"><i class="fas fa-clock"></i> My Disponibilities</h4>
|
|
||||||
<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>
|
|
||||||
</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>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<a href="{{ url_for('users.profile') }}" class="btn btn-secondary">Cancel</a>
|
<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>
|
||||||
@@ -165,177 +146,6 @@ document.addEventListener('DOMContentLoaded', function() {
|
|||||||
|
|
||||||
// Show gamertag inputs for already selected games
|
// Show gamertag inputs for already selected games
|
||||||
toggleGamertagInputs();
|
toggleGamertagInputs();
|
||||||
|
|
||||||
{% if user.role == 'player' %}
|
|
||||||
renderDisponibilityGrid();
|
|
||||||
{% endif %}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
{% if user.role == 'player' %}
|
|
||||||
// Generate time slots from 5pm (17:00) to 12am (24:00)
|
|
||||||
var TIME_SLOTS = [];
|
|
||||||
for (var h = 17; h <= 24; h++) {
|
|
||||||
for (var m = 0; m < 60; m += 30) {
|
|
||||||
if (h === 24 && m > 0) continue;
|
|
||||||
var displayHour;
|
|
||||||
var displayAmpm;
|
|
||||||
if (h === 24) {
|
|
||||||
displayHour = 12;
|
|
||||||
displayAmpm = 'AM';
|
|
||||||
} else if (h > 12) {
|
|
||||||
displayHour = h - 12;
|
|
||||||
displayAmpm = 'PM';
|
|
||||||
} else {
|
|
||||||
displayHour = h;
|
|
||||||
displayAmpm = 'PM';
|
|
||||||
}
|
|
||||||
var timeStr = (h < 10 ? '0' : '') + h + ':' + (m < 10 ? '0' : '') + m;
|
|
||||||
var displayTime = displayHour + ':' + (m < 10 ? '0' : '') + m + ' ' + displayAmpm;
|
|
||||||
TIME_SLOTS.push({ time: timeStr, display: displayTime });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var DAYS = [
|
|
||||||
{ value: 0, name: 'Monday' },
|
|
||||||
{ value: 1, name: 'Tuesday' },
|
|
||||||
{ value: 2, name: 'Wednesday' },
|
|
||||||
{ value: 3, name: 'Thursday' },
|
|
||||||
{ value: 4, name: 'Friday' },
|
|
||||||
{ value: 5, name: 'Saturday' },
|
|
||||||
{ value: 6, name: 'Sunday' }
|
|
||||||
];
|
|
||||||
|
|
||||||
// Store selected slots: {day: [time, time, ...]}
|
|
||||||
var selectedSlots = {};
|
|
||||||
|
|
||||||
function renderDisponibilityGrid() {
|
|
||||||
var grid = document.getElementById('disponibilities-grid');
|
|
||||||
grid.innerHTML = '<div style="margin-bottom: 10px;"><strong>Click time blocks to select your available hours</strong></div>';
|
|
||||||
|
|
||||||
var container = document.createElement('div');
|
|
||||||
container.className = 'disponibility-grid';
|
|
||||||
|
|
||||||
DAYS.forEach(function(day) {
|
|
||||||
var dayRow = document.createElement('div');
|
|
||||||
dayRow.className = 'disponibility-day-row';
|
|
||||||
|
|
||||||
var dayLabel = document.createElement('div');
|
|
||||||
dayLabel.className = 'disponibility-day-label';
|
|
||||||
dayLabel.textContent = day.name;
|
|
||||||
dayRow.appendChild(dayLabel);
|
|
||||||
|
|
||||||
var timeBlocks = document.createElement('div');
|
|
||||||
timeBlocks.className = 'disponibility-time-blocks';
|
|
||||||
|
|
||||||
TIME_SLOTS.forEach(function(slot) {
|
|
||||||
var block = document.createElement('div');
|
|
||||||
block.className = 'disponibility-time-block';
|
|
||||||
block.dataset.day = day.value;
|
|
||||||
block.dataset.time = slot.time;
|
|
||||||
block.textContent = slot.display;
|
|
||||||
block.onclick = function() {
|
|
||||||
toggleSlot(day.value, slot.time, block);
|
|
||||||
};
|
|
||||||
timeBlocks.appendChild(block);
|
|
||||||
});
|
|
||||||
|
|
||||||
dayRow.appendChild(timeBlocks);
|
|
||||||
container.appendChild(dayRow);
|
|
||||||
});
|
|
||||||
|
|
||||||
grid.appendChild(container);
|
|
||||||
loadMyDisponibilities();
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleSlot(day, time, element) {
|
|
||||||
if (!selectedSlots[day]) selectedSlots[day] = [];
|
|
||||||
|
|
||||||
var index = selectedSlots[day].indexOf(time);
|
|
||||||
if (index > -1) {
|
|
||||||
selectedSlots[day].splice(index, 1);
|
|
||||||
element.classList.remove('selected');
|
|
||||||
} else {
|
|
||||||
selectedSlots[day].push(time);
|
|
||||||
element.classList.add('selected');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadMyDisponibilities() {
|
|
||||||
fetch('{{ url_for("users.get_my_disponibilities") }}')
|
|
||||||
.then(function(response) { return response.json(); })
|
|
||||||
.then(function(data) {
|
|
||||||
selectedSlots = {};
|
|
||||||
|
|
||||||
for (var day in data) {
|
|
||||||
var slots = data[day];
|
|
||||||
slots.forEach(function(slot) {
|
|
||||||
selectedSlots[day] = selectedSlots[day] || [];
|
|
||||||
selectedSlots[day].push(slot.start_time);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
document.querySelectorAll('.disponibility-time-block').forEach(function(block) {
|
|
||||||
var day = block.dataset.day;
|
|
||||||
var time = block.dataset.time;
|
|
||||||
if (selectedSlots[day] && selectedSlots[day].indexOf(time) > -1) {
|
|
||||||
block.classList.add('selected');
|
|
||||||
} else {
|
|
||||||
block.classList.remove('selected');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch(function(error) {
|
|
||||||
console.error('Error loading disponibilities:', error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveDisponibilities() {
|
|
||||||
var slots = [];
|
|
||||||
for (var day in selectedSlots) {
|
|
||||||
selectedSlots[day].forEach(function(time) {
|
|
||||||
slots.push({ day_of_week: parseInt(day), start_time: time });
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fetch('{{ url_for("users.add_disponibilities_bulk") }}', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json'
|
|
||||||
},
|
|
||||||
body: JSON.stringify({ slots: slots })
|
|
||||||
})
|
|
||||||
.then(function(response) { return response.json(); })
|
|
||||||
.then(function(data) {
|
|
||||||
if (data.success) {
|
|
||||||
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!';
|
|
||||||
document.getElementById('disponibilities-grid').appendChild(msg);
|
|
||||||
setTimeout(function() { msg.remove(); }, 3000);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch(function(error) {
|
|
||||||
console.error('Error saving disponibilities:', error);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearDisponibilities() {
|
|
||||||
if (!confirm('Are you sure you want to clear all your disponibilities?')) return;
|
|
||||||
|
|
||||||
fetch('{{ url_for("users.clear_disponibilities") }}', {
|
|
||||||
method: 'POST'
|
|
||||||
})
|
|
||||||
.then(function(response) { return response.json(); })
|
|
||||||
.then(function(data) {
|
|
||||||
if (data.success) {
|
|
||||||
selectedSlots = {};
|
|
||||||
document.querySelectorAll('.disponibility-time-block').forEach(function(block) {
|
|
||||||
block.classList.remove('selected');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
{% endif %}
|
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -180,5 +180,363 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Disponibilities Card (Players only) -->
|
||||||
|
{% 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>
|
||||||
|
</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>
|
||||||
|
<div id="disponibilities-grid">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
<!-- Coach Availability Card (Coaches only) -->
|
||||||
|
{% 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>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="availability-grid" id="availability-grid">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block scripts %}
|
||||||
|
{% if user.role == 'coach' %}
|
||||||
|
<style>
|
||||||
|
.availability-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(7, 1fr);
|
||||||
|
gap: 12px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.day-column {
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px;
|
||||||
|
min-height: 300px;
|
||||||
|
}
|
||||||
|
.day-header {
|
||||||
|
text-align: center;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 8px 0;
|
||||||
|
border-bottom: 1px solid var(--border-color);
|
||||||
|
margin-bottom: 10px;
|
||||||
|
color: var(--primary);
|
||||||
|
}
|
||||||
|
.time-slot {
|
||||||
|
padding: 6px 8px;
|
||||||
|
margin: 4px 0;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 0.8rem;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: var(--transition);
|
||||||
|
background: var(--bg-secondary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
|
}
|
||||||
|
.time-slot:hover { background: var(--primary-light); border-color: var(--primary); }
|
||||||
|
.time-slot.selected { background: var(--primary); color: white; border-color: var(--primary-dark); }
|
||||||
|
.time-slot.selected:hover { background: var(--danger); }
|
||||||
|
@media (max-width: 768px) { .availability-grid { grid-template-columns: repeat(3, 1fr); } }
|
||||||
|
@media (max-width: 480px) { .availability-grid { grid-template-columns: 1fr; } }
|
||||||
|
</style>
|
||||||
|
<script>
|
||||||
|
const COACH_TIME_SLOTS = [];
|
||||||
|
for (let h = 8; h <= 22; h++) {
|
||||||
|
for (let m = 0; m < 60; m += 30) {
|
||||||
|
const timeStr = (h < 10 ? '0' : '') + h + ':' + (m < 10 ? '0' : '') + m;
|
||||||
|
const displayHour = h > 12 ? h - 12 : h;
|
||||||
|
const displayAmpm = h >= 12 ? 'PM' : 'AM';
|
||||||
|
const displayTime = displayHour + ':' + (m < 10 ? '0' : '') + m + ' ' + displayAmpm;
|
||||||
|
COACH_TIME_SLOTS.push({ time: timeStr, display: displayTime });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const COACH_DAYS = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'];
|
||||||
|
let coachSelectedSlots = {};
|
||||||
|
|
||||||
|
function loadCoachAvailability() {
|
||||||
|
{% for av in existing_availability %}
|
||||||
|
if (!coachSelectedSlots[{{ av.day_of_week }}]) coachSelectedSlots[{{ av.day_of_week }}] = [];
|
||||||
|
coachSelectedSlots[{{ av.day_of_week }}].push('{{ av.start_time.strftime('%H:%M') }}');
|
||||||
|
{% endfor %}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderCoachGrid() {
|
||||||
|
const grid = document.getElementById('availability-grid');
|
||||||
|
let html = '';
|
||||||
|
COACH_DAYS.forEach((day, dayIndex) => {
|
||||||
|
html += '<div class="day-column">';
|
||||||
|
html += '<div class="day-header">' + day.substring(0, 3) + '</div>';
|
||||||
|
COACH_TIME_SLOTS.forEach(slot => {
|
||||||
|
const isSelected = coachSelectedSlots[dayIndex] && coachSelectedSlots[dayIndex].includes(slot.time);
|
||||||
|
const cssClass = isSelected ? 'time-slot selected' : 'time-slot';
|
||||||
|
html += '<div class="' + cssClass + '" data-day="' + dayIndex + '" data-time="' + slot.time + '" onclick="toggleCoachSlot(' + dayIndex + ', \'' + slot.time + '\', this)">' + slot.display + '</div>';
|
||||||
|
});
|
||||||
|
html += '</div>';
|
||||||
|
});
|
||||||
|
grid.innerHTML = html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleCoachSlot(dayOfWeek, timeStr, element) {
|
||||||
|
if (!coachSelectedSlots[dayOfWeek]) coachSelectedSlots[dayOfWeek] = [];
|
||||||
|
const index = coachSelectedSlots[dayOfWeek].indexOf(timeStr);
|
||||||
|
if (index === -1) {
|
||||||
|
coachSelectedSlots[dayOfWeek].push(timeStr);
|
||||||
|
element.classList.add('selected');
|
||||||
|
} else {
|
||||||
|
coachSelectedSlots[dayOfWeek].splice(index, 1);
|
||||||
|
element.classList.remove('selected');
|
||||||
|
}
|
||||||
|
clearTimeout(window._coachSaveTimeout);
|
||||||
|
window._coachSaveTimeout = setTimeout(saveCoachAvailability, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveCoachAvailability() {
|
||||||
|
const slots = [];
|
||||||
|
for (let day in coachSelectedSlots) {
|
||||||
|
coachSelectedSlots[day].forEach(time => {
|
||||||
|
slots.push({ day_of_week: parseInt(day), start_time: time });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
fetch('{{ url_for("users.manage_coach_availability") }}', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ slots: slots })
|
||||||
|
})
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
const msg = document.createElement('div');
|
||||||
|
msg.className = 'alert alert-success';
|
||||||
|
msg.style.marginTop = '10px';
|
||||||
|
msg.innerHTML = '<i class="fas fa-check"></i> Availability saved!';
|
||||||
|
document.getElementById('availability-grid').appendChild(msg);
|
||||||
|
setTimeout(() => msg.remove(), 3000);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(err => console.error('Save error:', err));
|
||||||
|
}
|
||||||
|
|
||||||
|
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' })
|
||||||
|
.then(r => r.json())
|
||||||
|
.then(data => {
|
||||||
|
if (data.success) {
|
||||||
|
coachSelectedSlots = {};
|
||||||
|
renderCoachGrid();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
loadCoachAvailability();
|
||||||
|
renderCoachGrid();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
{% if user.role == 'player' %}
|
||||||
|
<script>
|
||||||
|
// Generate time slots from 5pm (17:00) to 12am (24:00)
|
||||||
|
var TIME_SLOTS = [];
|
||||||
|
for (var h = 17; h <= 24; h++) {
|
||||||
|
for (var m = 0; m < 60; m += 30) {
|
||||||
|
if (h === 24 && m > 0) continue;
|
||||||
|
var displayHour;
|
||||||
|
var displayAmpm;
|
||||||
|
if (h === 24) {
|
||||||
|
displayHour = 12;
|
||||||
|
displayAmpm = 'AM';
|
||||||
|
} else if (h > 12) {
|
||||||
|
displayHour = h - 12;
|
||||||
|
displayAmpm = 'PM';
|
||||||
|
} else {
|
||||||
|
displayHour = h;
|
||||||
|
displayAmpm = 'PM';
|
||||||
|
}
|
||||||
|
var timeStr = (h < 10 ? '0' : '') + h + ':' + (m < 10 ? '0' : '') + m;
|
||||||
|
var displayTime = displayHour + ':' + (m < 10 ? '0' : '') + m + ' ' + displayAmpm;
|
||||||
|
TIME_SLOTS.push({ time: timeStr, display: displayTime });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var DAYS = [
|
||||||
|
{ value: 0, name: 'Monday' },
|
||||||
|
{ value: 1, name: 'Tuesday' },
|
||||||
|
{ value: 2, name: 'Wednesday' },
|
||||||
|
{ value: 3, name: 'Thursday' },
|
||||||
|
{ value: 4, name: 'Friday' },
|
||||||
|
{ value: 5, name: 'Saturday' },
|
||||||
|
{ value: 6, name: 'Sunday' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// Store selected slots: {day: [time, time, ...]}
|
||||||
|
var selectedSlots = {};
|
||||||
|
|
||||||
|
function renderDisponibilityGrid() {
|
||||||
|
var grid = document.getElementById('disponibilities-grid');
|
||||||
|
grid.innerHTML = '<div style="margin-bottom: 10px;"><strong>Click time blocks to select your available hours</strong></div>';
|
||||||
|
|
||||||
|
var container = document.createElement('div');
|
||||||
|
container.className = 'disponibility-grid';
|
||||||
|
|
||||||
|
DAYS.forEach(function(day) {
|
||||||
|
var dayRow = document.createElement('div');
|
||||||
|
dayRow.className = 'disponibility-day-row';
|
||||||
|
|
||||||
|
var dayLabel = document.createElement('div');
|
||||||
|
dayLabel.className = 'disponibility-day-label';
|
||||||
|
dayLabel.textContent = day.name;
|
||||||
|
dayRow.appendChild(dayLabel);
|
||||||
|
|
||||||
|
var timeBlocks = document.createElement('div');
|
||||||
|
timeBlocks.className = 'disponibility-time-blocks';
|
||||||
|
|
||||||
|
TIME_SLOTS.forEach(function(slot) {
|
||||||
|
var block = document.createElement('div');
|
||||||
|
block.className = 'disponibility-time-block';
|
||||||
|
block.dataset.day = day.value;
|
||||||
|
block.dataset.time = slot.time;
|
||||||
|
block.textContent = slot.display;
|
||||||
|
block.onclick = function() {
|
||||||
|
toggleSlot(day.value, slot.time, block);
|
||||||
|
};
|
||||||
|
timeBlocks.appendChild(block);
|
||||||
|
});
|
||||||
|
|
||||||
|
dayRow.appendChild(timeBlocks);
|
||||||
|
container.appendChild(dayRow);
|
||||||
|
});
|
||||||
|
|
||||||
|
grid.appendChild(container);
|
||||||
|
loadMyDisponibilities();
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleSlot(day, time, element) {
|
||||||
|
if (!selectedSlots[day]) selectedSlots[day] = [];
|
||||||
|
|
||||||
|
var index = selectedSlots[day].indexOf(time);
|
||||||
|
if (index > -1) {
|
||||||
|
selectedSlots[day].splice(index, 1);
|
||||||
|
element.classList.remove('selected');
|
||||||
|
} else {
|
||||||
|
selectedSlots[day].push(time);
|
||||||
|
element.classList.add('selected');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadMyDisponibilities() {
|
||||||
|
fetch('{{ url_for("users.get_my_disponibilities") }}')
|
||||||
|
.then(function(response) { return response.json(); })
|
||||||
|
.then(function(data) {
|
||||||
|
selectedSlots = {};
|
||||||
|
|
||||||
|
for (var day in data) {
|
||||||
|
var slots = data[day];
|
||||||
|
slots.forEach(function(slot) {
|
||||||
|
selectedSlots[day] = selectedSlots[day] || [];
|
||||||
|
selectedSlots[day].push(slot.start_time);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll('.disponibility-time-block').forEach(function(block) {
|
||||||
|
var day = block.dataset.day;
|
||||||
|
var time = block.dataset.time;
|
||||||
|
if (selectedSlots[day] && selectedSlots[day].indexOf(time) > -1) {
|
||||||
|
block.classList.add('selected');
|
||||||
|
} else {
|
||||||
|
block.classList.remove('selected');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(function(error) {
|
||||||
|
console.error('Error loading disponibilities:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveDisponibilities() {
|
||||||
|
var slots = [];
|
||||||
|
for (var day in selectedSlots) {
|
||||||
|
selectedSlots[day].forEach(function(time) {
|
||||||
|
slots.push({ day_of_week: parseInt(day), start_time: time });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fetch('{{ url_for("users.add_disponibilities_bulk") }}', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-CSRFToken': '{{ csrf_token() }}'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ slots: slots })
|
||||||
|
})
|
||||||
|
.then(function(response) { return response.json(); })
|
||||||
|
.then(function(data) {
|
||||||
|
if (data.success) {
|
||||||
|
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!';
|
||||||
|
document.getElementById('disponibilities-grid').appendChild(msg);
|
||||||
|
setTimeout(function() { msg.remove(); }, 3000);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(function(error) {
|
||||||
|
console.error('Error saving disponibilities:', error);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearDisponibilities() {
|
||||||
|
if (!confirm('Are you sure you want to clear all your disponibilities?')) return;
|
||||||
|
|
||||||
|
fetch('{{ url_for("users.clear_disponibilities") }}', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'X-CSRFToken': '{{ csrf_token() }}'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(function(response) { return response.json(); })
|
||||||
|
.then(function(data) {
|
||||||
|
if (data.success) {
|
||||||
|
selectedSlots = {};
|
||||||
|
document.querySelectorAll('.disponibility-time-block').forEach(function(block) {
|
||||||
|
block.classList.remove('selected');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize on page load
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
renderDisponibilityGrid();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endif %}
|
||||||
|
{% endblock %}
|
||||||
@@ -34,10 +34,15 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-group col-6">
|
<div class="form-group col-3">
|
||||||
<label for="date">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>
|
<input type="date" id="date" name="date" value="{{ tryout.date.strftime('%Y-%m-%d') if tryout else '' }}" required>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group col-3">
|
||||||
|
<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>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-row">
|
<div class="form-row">
|
||||||
<div class="form-group col-6">
|
<div class="form-group col-6">
|
||||||
|
|||||||
@@ -29,7 +29,13 @@
|
|||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<i class="fas fa-calendar info-icon"></i>
|
<i class="fas fa-calendar info-icon"></i>
|
||||||
<span class="info-label">Date</span>
|
<span class="info-label">Date</span>
|
||||||
<span class="info-value">{{ tryout.date.strftime('%b %d, %Y') }}</span>
|
<span class="info-value">
|
||||||
|
{% if tryout.end_date and tryout.end_date != tryout.date %}
|
||||||
|
{{ tryout.date.strftime('%b %d') }} - {{ tryout.end_date.strftime('%b %d, %Y') }}
|
||||||
|
{% else %}
|
||||||
|
{{ tryout.date.strftime('%b %d, %Y') }}
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="info-item">
|
<div class="info-item">
|
||||||
<i class="fas fa-map-marker-alt info-icon"></i>
|
<i class="fas fa-map-marker-alt info-icon"></i>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3>Tryout Details</h3>
|
<h3>Tryout Details</h3>
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
{% if can_edit %}
|
{% 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">
|
<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>
|
</a>
|
||||||
@@ -24,11 +24,19 @@
|
|||||||
</select>
|
</select>
|
||||||
</form>
|
</form>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% if can_edit %}
|
{% 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">
|
<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>
|
</a>
|
||||||
{% endif %}
|
{% 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.');">
|
||||||
|
<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
|
||||||
|
</button>
|
||||||
|
</form>
|
||||||
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
@@ -39,7 +47,13 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="detail-item">
|
<div class="detail-item">
|
||||||
<span class="detail-label">Date</span>
|
<span class="detail-label">Date</span>
|
||||||
<span class="detail-value">{{ tryout.date.strftime('%A, %B %d, %Y') }}</span>
|
<span class="detail-value">
|
||||||
|
{% if tryout.end_date and tryout.end_date != tryout.date %}
|
||||||
|
{{ tryout.date.strftime('%B %d') }} - {{ tryout.end_date.strftime('%B %d, %Y') }}
|
||||||
|
{% else %}
|
||||||
|
{{ tryout.date.strftime('%A, %B %d, %Y') }}
|
||||||
|
{% endif %}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="detail-item">
|
<div class="detail-item">
|
||||||
<span class="detail-label">Location</span>
|
<span class="detail-label">Location</span>
|
||||||
@@ -90,7 +104,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% if can_edit %}
|
{% if can_edit and not tryout.is_ended %}
|
||||||
<div class="card mb-4">
|
<div class="card mb-4">
|
||||||
<div class="card-header">
|
<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>
|
||||||
@@ -176,12 +190,14 @@
|
|||||||
<i class="fas fa-sticky-note"></i>
|
<i class="fas fa-sticky-note"></i>
|
||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% 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" onsubmit="return confirm('Remove {{ p.username }} from this tryout? This will also remove them from all teams and matches within this tryout.');">
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||||
<button type="submit" class="btn btn-sm btn-danger" title="Remove player from tryout">
|
<button type="submit" class="btn btn-sm btn-danger" title="Remove player from tryout">
|
||||||
<i class="fas fa-user-minus"></i> Remove
|
<i class="fas fa-user-minus"></i> Remove
|
||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
{% endif %}
|
||||||
</td>
|
</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</tr>
|
</tr>
|
||||||
@@ -201,14 +217,14 @@
|
|||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header">
|
<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 %}
|
{% if can_edit and not tryout.is_ended %}
|
||||||
<button class="btn btn-sm btn-primary" onclick="showCreateTeam()">
|
<button class="btn btn-sm btn-primary" onclick="showCreateTeam()">
|
||||||
<i class="fas fa-plus"></i> New Team
|
<i class="fas fa-plus"></i> New Team
|
||||||
</button>
|
</button>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
{% if can_edit %}
|
{% if can_edit and not tryout.is_ended %}
|
||||||
<div id="createTeamForm" class="hidden mb-3">
|
<div id="createTeamForm" class="hidden mb-3">
|
||||||
<form method="POST" action="{{ url_for('tryouts.create_team', tryout_id=tryout.id) }}" class="form-inline">
|
<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="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||||
@@ -235,7 +251,7 @@
|
|||||||
<li class="text-muted">No players assigned yet.</li>
|
<li class="text-muted">No players assigned yet.</li>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</ul>
|
</ul>
|
||||||
{% if can_edit and registered_players %}
|
{% if can_edit and not tryout.is_ended and registered_players %}
|
||||||
{% set positions = game_positions.get(tryout.game, []) %}
|
{% set positions = game_positions.get(tryout.game, []) %}
|
||||||
<form method="POST" action="{{ url_for('tryouts.add_to_team', tryout_id=tryout.id, team_id=team.team.id) }}" class="form-inline mt-2">
|
<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() }}"/>
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||||
@@ -272,7 +288,7 @@
|
|||||||
<div class="card tryout-schedule-card mb-4">
|
<div class="card tryout-schedule-card mb-4">
|
||||||
<div class="card-header">
|
<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 %}
|
{% if can_edit and not tryout.is_ended %}
|
||||||
<div class="card-actions">
|
<div class="card-actions">
|
||||||
<a href="{{ url_for('matches.create_match', tryout_id=tryout.id) }}" class="btn btn-sm btn-success">
|
<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
|
||||||
@@ -412,9 +428,11 @@
|
|||||||
</td>
|
</td>
|
||||||
{% if can_edit %}
|
{% if can_edit %}
|
||||||
<td>
|
<td>
|
||||||
|
{% if not tryout.is_ended %}
|
||||||
<a href="{{ url_for('matches.edit_match', match_id=m.id) }}" class="btn btn-sm btn-outline">
|
<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>
|
</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">
|
<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
|
<i class="fas fa-sticky-note"></i> Note
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
"""Migration: Add end_date column to tryouts table.
|
||||||
|
|
||||||
|
Run this script to add the end_date column to the tryouts table.
|
||||||
|
Usage: python migrations/add_tryout_end_date.py
|
||||||
|
"""
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import os
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
from app.app import create_app
|
||||||
|
from app.extensions import db
|
||||||
|
from sqlalchemy import text
|
||||||
|
|
||||||
|
app = create_app()
|
||||||
|
|
||||||
|
with app.app_context():
|
||||||
|
# Check if column already exists
|
||||||
|
result = db.session.execute(text(
|
||||||
|
"SELECT COUNT(*) FROM information_schema.columns "
|
||||||
|
"WHERE table_name = 'tryouts' AND column_name = 'end_date'"
|
||||||
|
))
|
||||||
|
exists = result.scalar() > 0
|
||||||
|
|
||||||
|
if exists:
|
||||||
|
print("Column 'end_date' already exists in 'tryouts' table. Skipping.")
|
||||||
|
else:
|
||||||
|
db.session.execute(text(
|
||||||
|
"ALTER TABLE tryouts ADD COLUMN end_date DATE NULL"
|
||||||
|
))
|
||||||
|
# Backfill: set end_date = date for existing tryouts
|
||||||
|
db.session.execute(text(
|
||||||
|
"UPDATE tryouts SET end_date = date WHERE end_date IS NULL"
|
||||||
|
))
|
||||||
|
db.session.commit()
|
||||||
|
print("Successfully added 'end_date' column to 'tryouts' table and backfilled existing rows.")
|
||||||
|
|
||||||
|
print("Migration complete.")
|
||||||
Reference in New Issue
Block a user