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:
Binary file not shown.
@@ -11,7 +11,7 @@ from urllib.parse import quote
|
|||||||
|
|
||||||
|
|
||||||
# Available user roles in the system
|
# Available user roles in the system
|
||||||
ROLES = ['president', 'manager', 'coach', 'player', 'scout']
|
ROLES = ['admin', 'manager', 'coach', 'player', 'scout']
|
||||||
|
|
||||||
# Popular E-Sports games list for player profiles
|
# Popular E-Sports games list for player profiles
|
||||||
ESPORT_GAMES = [
|
ESPORT_GAMES = [
|
||||||
@@ -123,7 +123,7 @@ class User(UserMixin, db.Model):
|
|||||||
Returns:
|
Returns:
|
||||||
bool: True if user is president, manager, or coach.
|
bool: True if user is president, manager, or coach.
|
||||||
"""
|
"""
|
||||||
return self.role in ['president', 'manager', 'coach']
|
return self.role in ['admin', 'manager', 'coach']
|
||||||
|
|
||||||
def can_manage_users(self):
|
def can_manage_users(self):
|
||||||
"""Check if user can manage (create/delete) other users.
|
"""Check if user can manage (create/delete) other users.
|
||||||
@@ -131,7 +131,7 @@ class User(UserMixin, db.Model):
|
|||||||
Returns:
|
Returns:
|
||||||
bool: True if user is president.
|
bool: True if user is president.
|
||||||
"""
|
"""
|
||||||
return self.role == 'president'
|
return self.role == 'admin'
|
||||||
|
|
||||||
def can_manage_tryouts(self):
|
def can_manage_tryouts(self):
|
||||||
"""Check if user can manage tryouts.
|
"""Check if user can manage tryouts.
|
||||||
@@ -139,7 +139,7 @@ class User(UserMixin, db.Model):
|
|||||||
Returns:
|
Returns:
|
||||||
bool: True if user is president, manager or coach.
|
bool: True if user is president, manager or coach.
|
||||||
"""
|
"""
|
||||||
return self.role in ['president', 'manager', 'coach']
|
return self.role in ['admin', 'manager', 'coach']
|
||||||
|
|
||||||
def can_manage_teams(self):
|
def can_manage_teams(self):
|
||||||
"""Check if user can manage organization teams.
|
"""Check if user can manage organization teams.
|
||||||
@@ -147,7 +147,7 @@ class User(UserMixin, db.Model):
|
|||||||
Returns:
|
Returns:
|
||||||
bool: True if user is president or manager.
|
bool: True if user is president or manager.
|
||||||
"""
|
"""
|
||||||
return self.role in ['president', 'manager']
|
return self.role in ['admin', 'manager']
|
||||||
|
|
||||||
def can_schedule_matches(self):
|
def can_schedule_matches(self):
|
||||||
"""Check if user can schedule matches.
|
"""Check if user can schedule matches.
|
||||||
@@ -155,7 +155,7 @@ class User(UserMixin, db.Model):
|
|||||||
Returns:
|
Returns:
|
||||||
bool: True if user is president, manager, or coach.
|
bool: True if user is president, manager, or coach.
|
||||||
"""
|
"""
|
||||||
return self.role in ['president', 'manager', 'coach']
|
return self.role in ['admin', 'manager', 'coach']
|
||||||
|
|
||||||
def can_manage_this_tryout(self, tryout):
|
def can_manage_this_tryout(self, tryout):
|
||||||
"""Check if user can manage a specific tryout.
|
"""Check if user can manage a specific tryout.
|
||||||
@@ -170,7 +170,7 @@ class User(UserMixin, db.Model):
|
|||||||
Returns:
|
Returns:
|
||||||
bool: True if user has permission to manage the tryout.
|
bool: True if user has permission to manage the tryout.
|
||||||
"""
|
"""
|
||||||
if self.role == 'president':
|
if self.role == 'admin':
|
||||||
return True
|
return True
|
||||||
if self.role == 'manager' and (tryout.created_by == self.id or tryout.manager_id == self.id):
|
if self.role == 'manager' and (tryout.created_by == self.id or tryout.manager_id == self.id):
|
||||||
return True
|
return True
|
||||||
@@ -203,7 +203,7 @@ class User(UserMixin, db.Model):
|
|||||||
Returns:
|
Returns:
|
||||||
bool: True if user has permission to manage the org team.
|
bool: True if user has permission to manage the org team.
|
||||||
"""
|
"""
|
||||||
if self.role == 'president':
|
if self.role == 'admin':
|
||||||
return True
|
return True
|
||||||
if self.role == 'manager':
|
if self.role == 'manager':
|
||||||
return True # Managers can manage all org teams (create/edit/delete)
|
return True # Managers can manage all org teams (create/edit/delete)
|
||||||
@@ -778,7 +778,7 @@ class Contract(db.Model):
|
|||||||
if user.id == self.player_id:
|
if user.id == self.player_id:
|
||||||
return True
|
return True
|
||||||
# President can view all contracts
|
# President can view all contracts
|
||||||
if user.role == 'president':
|
if user.role == 'admin':
|
||||||
return True
|
return True
|
||||||
# Manager can view contracts for players on their teams
|
# Manager can view contracts for players on their teams
|
||||||
if user.role == 'manager':
|
if user.role == 'manager':
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ def list_evaluations():
|
|||||||
else:
|
else:
|
||||||
sort_expr = sort_expr.desc()
|
sort_expr = sort_expr.desc()
|
||||||
|
|
||||||
if user.role == 'president':
|
if user.role == 'admin':
|
||||||
evaluations = Evaluation.query \
|
evaluations = Evaluation.query \
|
||||||
.outerjoin(Tryout, Evaluation.tryout_id == Tryout.id) \
|
.outerjoin(Tryout, Evaluation.tryout_id == Tryout.id) \
|
||||||
.outerjoin(player_alias, Evaluation.player_id == player_alias.id) \
|
.outerjoin(player_alias, Evaluation.player_id == player_alias.id) \
|
||||||
@@ -227,7 +227,7 @@ def evaluate_player(tryout_id, player_id):
|
|||||||
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
|
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
|
||||||
|
|
||||||
evaluators = None
|
evaluators = None
|
||||||
if current_user.role == 'president':
|
if current_user.role == 'admin':
|
||||||
all_evaluations = Evaluation.query.filter_by(tryout_id=tryout_id, player_id=player_id).all()
|
all_evaluations = Evaluation.query.filter_by(tryout_id=tryout_id, player_id=player_id).all()
|
||||||
evaluators = [{'evaluator': User.query.get(e.evaluator_id), 'eval': e} for e in all_evaluations]
|
evaluators = [{'evaluator': User.query.get(e.evaluator_id), 'eval': e} for e in all_evaluations]
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -43,7 +43,7 @@ def dashboard():
|
|||||||
user = current_user
|
user = current_user
|
||||||
stats = {}
|
stats = {}
|
||||||
|
|
||||||
if user.role == 'president':
|
if user.role == 'admin':
|
||||||
stats['total_users'] = User.query.count()
|
stats['total_users'] = User.query.count()
|
||||||
stats['total_players'] = User.query.filter_by(role='player').count()
|
stats['total_players'] = User.query.filter_by(role='player').count()
|
||||||
stats['total_tryouts'] = Tryout.query.count()
|
stats['total_tryouts'] = Tryout.query.count()
|
||||||
|
|||||||
+6
-4
@@ -19,7 +19,7 @@ def can_schedule_match():
|
|||||||
Returns:
|
Returns:
|
||||||
bool: True if user is president, manager, coach, or scout.
|
bool: True if user is president, manager, coach, or scout.
|
||||||
"""
|
"""
|
||||||
return current_user.role in ['president', 'manager', 'coach', 'scout']
|
return current_user.role in ['admin', 'manager', 'coach', 'scout']
|
||||||
|
|
||||||
|
|
||||||
@matches_bp.route('/calendar')
|
@matches_bp.route('/calendar')
|
||||||
@@ -248,7 +248,7 @@ def get_visible_tryouts_for_user():
|
|||||||
Returns:
|
Returns:
|
||||||
list: Query result of Tryout objects.
|
list: Query result of Tryout objects.
|
||||||
"""
|
"""
|
||||||
if current_user.role == 'president':
|
if current_user.role == 'admin':
|
||||||
return Tryout.query.order_by(Tryout.date).all()
|
return Tryout.query.order_by(Tryout.date).all()
|
||||||
elif current_user.role == 'manager':
|
elif current_user.role == 'manager':
|
||||||
return Tryout.query.filter_by(created_by=current_user.id).order_by(Tryout.date).all()
|
return Tryout.query.filter_by(created_by=current_user.id).order_by(Tryout.date).all()
|
||||||
@@ -558,8 +558,10 @@ def edit_match(match_id):
|
|||||||
# Handle player vs player matches - update participants
|
# Handle player vs player matches - update participants
|
||||||
elif match.match_type == 'player_vs_player':
|
elif match.match_type == 'player_vs_player':
|
||||||
MatchParticipant.query.filter_by(match_id=match.id).delete()
|
MatchParticipant.query.filter_by(match_id=match.id).delete()
|
||||||
team1_player_ids = request.form.getlist('team1_player_ids')
|
team1_str = request.form.get('team1_player_ids', '')
|
||||||
team2_player_ids = request.form.getlist('team2_player_ids')
|
team2_str = request.form.get('team2_player_ids', '')
|
||||||
|
team1_player_ids = [p for p in team1_str.split(',') if p.strip()] if team1_str else []
|
||||||
|
team2_player_ids = [p for p in team2_str.split(',') if p.strip()] if team2_str else []
|
||||||
notified_participant_ids = []
|
notified_participant_ids = []
|
||||||
for pid in team1_player_ids:
|
for pid in team1_player_ids:
|
||||||
participant = MatchParticipant(match_id=match.id, player_id=int(pid), team_side=1)
|
participant = MatchParticipant(match_id=match.id, player_id=int(pid), team_side=1)
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ def can_manage_team_match(team):
|
|||||||
Returns:
|
Returns:
|
||||||
bool: True if user is president, manager, or a coach of this team.
|
bool: True if user is president, manager, or a coach of this team.
|
||||||
"""
|
"""
|
||||||
if current_user.role in ['president']:
|
if current_user.role in ['admin']:
|
||||||
return True
|
return True
|
||||||
if current_user.role == 'manager':
|
if current_user.role == 'manager':
|
||||||
return True
|
return True
|
||||||
@@ -45,7 +45,7 @@ def list_matches():
|
|||||||
# Optional pre-filter by team_id from query param
|
# Optional pre-filter by team_id from query param
|
||||||
filter_team_id = request.args.get('team_id', type=int)
|
filter_team_id = request.args.get('team_id', type=int)
|
||||||
|
|
||||||
if current_user.role == 'president':
|
if current_user.role == 'admin':
|
||||||
teams = OrgTeam.query.order_by(OrgTeam.name).all()
|
teams = OrgTeam.query.order_by(OrgTeam.name).all()
|
||||||
matches_query = TeamMatch.query
|
matches_query = TeamMatch.query
|
||||||
elif current_user.role == 'manager':
|
elif current_user.role == 'manager':
|
||||||
@@ -337,7 +337,7 @@ def api_manageable_teams():
|
|||||||
if not current_user.can_schedule_matches():
|
if not current_user.can_schedule_matches():
|
||||||
return jsonify([])
|
return jsonify([])
|
||||||
|
|
||||||
if current_user.role in ['president', 'manager']:
|
if current_user.role in ['admin', 'manager']:
|
||||||
teams = OrgTeam.query.order_by(OrgTeam.name).all()
|
teams = OrgTeam.query.order_by(OrgTeam.name).all()
|
||||||
elif current_user.role == 'coach':
|
elif current_user.role == 'coach':
|
||||||
teams = OrgTeam.query.filter(
|
teams = OrgTeam.query.filter(
|
||||||
|
|||||||
+41
-12
@@ -24,7 +24,9 @@ def list_teams():
|
|||||||
"""
|
"""
|
||||||
can_manage = current_user.can_manage_teams()
|
can_manage = current_user.can_manage_teams()
|
||||||
|
|
||||||
if current_user.role == 'coach':
|
if current_user.role == 'admin':
|
||||||
|
teams = OrgTeam.query.order_by(OrgTeam.name).all()
|
||||||
|
elif current_user.role == 'coach':
|
||||||
teams = OrgTeam.query.filter(
|
teams = OrgTeam.query.filter(
|
||||||
db.or_(
|
db.or_(
|
||||||
OrgTeam.coaches.any(id=current_user.id),
|
OrgTeam.coaches.any(id=current_user.id),
|
||||||
@@ -181,18 +183,45 @@ def edit_team(team_id):
|
|||||||
flash(f'Team "{name}" already exists.', 'danger')
|
flash(f'Team "{name}" already exists.', 'danger')
|
||||||
return redirect(url_for('teams.list_teams'))
|
return redirect(url_for('teams.list_teams'))
|
||||||
|
|
||||||
team.name = name
|
# Check if we're syncing staff (multi-select) or single legacy update
|
||||||
team.coach_id = int(coach_id) if coach_id else None
|
if request.form.get('sync_staff') == '1':
|
||||||
team.manager_id = int(manager_id) if manager_id else None
|
coach_ids = request.form.getlist('coach_ids')
|
||||||
|
manager_ids = request.form.getlist('manager_ids')
|
||||||
|
|
||||||
if coach_id:
|
# Sync coaches many-to-many
|
||||||
coach_user = User.query.get(int(coach_id))
|
team.coaches = []
|
||||||
if coach_user and not team.coaches.filter_by(id=coach_user.id).first():
|
for cid in coach_ids:
|
||||||
team.coaches.append(coach_user)
|
if cid and cid.strip():
|
||||||
if manager_id:
|
coach_user = User.query.get(int(cid))
|
||||||
manager_user = User.query.get(int(manager_id))
|
if coach_user and coach_user.role == 'coach':
|
||||||
if manager_user and not team.managers.filter_by(id=manager_user.id).first():
|
team.coaches.append(coach_user)
|
||||||
team.managers.append(manager_user)
|
# Update legacy coach_id with first coach
|
||||||
|
coach_list = team.coaches.all()
|
||||||
|
team.coach_id = coach_list[0].id if coach_list else None
|
||||||
|
|
||||||
|
# Sync managers many-to-many
|
||||||
|
team.managers = []
|
||||||
|
for mid in manager_ids:
|
||||||
|
if mid and mid.strip():
|
||||||
|
manager_user = User.query.get(int(mid))
|
||||||
|
if manager_user and manager_user.role == 'manager':
|
||||||
|
team.managers.append(manager_user)
|
||||||
|
# Update legacy manager_id with first manager
|
||||||
|
manager_list = team.managers.all()
|
||||||
|
team.manager_id = manager_list[0].id if manager_list else None
|
||||||
|
else:
|
||||||
|
# Legacy single dropdown update
|
||||||
|
team.coach_id = int(coach_id) if coach_id else None
|
||||||
|
team.manager_id = int(manager_id) if manager_id else None
|
||||||
|
|
||||||
|
if coach_id:
|
||||||
|
coach_user = User.query.get(int(coach_id))
|
||||||
|
if coach_user and not team.coaches.filter_by(id=coach_user.id).first():
|
||||||
|
team.coaches.append(coach_user)
|
||||||
|
if manager_id:
|
||||||
|
manager_user = User.query.get(int(manager_id))
|
||||||
|
if manager_user and not team.managers.filter_by(id=manager_user.id).first():
|
||||||
|
team.managers.append(manager_user)
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
flash(f'Team "{name}" updated successfully!', 'success')
|
flash(f'Team "{name}" updated successfully!', 'success')
|
||||||
|
|||||||
+16
-4
@@ -18,7 +18,7 @@ def can_manage():
|
|||||||
Returns:
|
Returns:
|
||||||
bool: True if user is president or manager.
|
bool: True if user is president or manager.
|
||||||
"""
|
"""
|
||||||
return current_user.role in ['president', 'manager']
|
return current_user.role in ['admin', 'manager']
|
||||||
|
|
||||||
|
|
||||||
@tryouts_bp.route('')
|
@tryouts_bp.route('')
|
||||||
@@ -35,7 +35,7 @@ def list_tryouts():
|
|||||||
Returns:
|
Returns:
|
||||||
Response: Rendered tryouts list template.
|
Response: Rendered tryouts list template.
|
||||||
"""
|
"""
|
||||||
if current_user.role == 'president':
|
if current_user.role == 'admin':
|
||||||
tryouts = Tryout.query.order_by(Tryout.date.desc()).all()
|
tryouts = Tryout.query.order_by(Tryout.date.desc()).all()
|
||||||
elif current_user.role == 'manager':
|
elif current_user.role == 'manager':
|
||||||
tryouts = Tryout.query.filter_by(created_by=current_user.id).order_by(Tryout.date.desc()).all()
|
tryouts = Tryout.query.filter_by(created_by=current_user.id).order_by(Tryout.date.desc()).all()
|
||||||
@@ -192,7 +192,7 @@ def view_tryout(tryout_id):
|
|||||||
|
|
||||||
# Check if user has permission to view this tryout
|
# Check if user has permission to view this tryout
|
||||||
can_view = False
|
can_view = False
|
||||||
if current_user.role == 'president':
|
if current_user.role == 'admin':
|
||||||
can_view = True
|
can_view = True
|
||||||
elif current_user.role == 'manager' and tryout.created_by == current_user.id:
|
elif current_user.role == 'manager' and tryout.created_by == current_user.id:
|
||||||
can_view = True
|
can_view = True
|
||||||
@@ -276,6 +276,17 @@ def view_tryout(tryout_id):
|
|||||||
confirmed_count = sum(1 for p in all_participants if p.attendance_confirmed)
|
confirmed_count = sum(1 for p in all_participants if p.attendance_confirmed)
|
||||||
total_count = len(all_participants)
|
total_count = len(all_participants)
|
||||||
|
|
||||||
|
# Build per-player presence data for toggle buttons
|
||||||
|
player_presence = []
|
||||||
|
for p in all_participants:
|
||||||
|
if p.player:
|
||||||
|
player_presence.append({
|
||||||
|
'participant_id': p.id,
|
||||||
|
'player_id': p.player_id,
|
||||||
|
'player_name': p.player.username,
|
||||||
|
'attendance_confirmed': p.attendance_confirmed
|
||||||
|
})
|
||||||
|
|
||||||
if match.match_type == 'team_vs_team':
|
if match.match_type == 'team_vs_team':
|
||||||
participants = {
|
participants = {
|
||||||
'team1': match.team1.name if match.team1 else 'TBD',
|
'team1': match.team1.name if match.team1 else 'TBD',
|
||||||
@@ -299,7 +310,8 @@ def view_tryout(tryout_id):
|
|||||||
'match': match,
|
'match': match,
|
||||||
'participants': participants,
|
'participants': participants,
|
||||||
'confirmed_count': confirmed_count,
|
'confirmed_count': confirmed_count,
|
||||||
'total_count': total_count
|
'total_count': total_count,
|
||||||
|
'player_presence': player_presence
|
||||||
})
|
})
|
||||||
|
|
||||||
return render_template('pages/view_tryout.html',
|
return render_template('pages/view_tryout.html',
|
||||||
|
|||||||
+13
-13
@@ -69,7 +69,7 @@ def list_users():
|
|||||||
Returns:
|
Returns:
|
||||||
Response: Rendered users list template or redirect to dashboard.
|
Response: Rendered users list template or redirect to dashboard.
|
||||||
"""
|
"""
|
||||||
if current_user.role != 'president':
|
if current_user.role != 'admin':
|
||||||
flash('Only the president can manage users.', 'danger')
|
flash('Only the president can manage users.', 'danger')
|
||||||
return redirect(url_for('main.dashboard'))
|
return redirect(url_for('main.dashboard'))
|
||||||
|
|
||||||
@@ -91,7 +91,7 @@ def edit_user(user_id):
|
|||||||
Returns:
|
Returns:
|
||||||
Response: Edit form or redirect to users list.
|
Response: Edit form or redirect to users list.
|
||||||
"""
|
"""
|
||||||
if current_user.role != 'president':
|
if current_user.role != 'admin':
|
||||||
flash('Only the president can edit users.', 'danger')
|
flash('Only the president can edit users.', 'danger')
|
||||||
return redirect(url_for('main.dashboard'))
|
return redirect(url_for('main.dashboard'))
|
||||||
|
|
||||||
@@ -149,7 +149,7 @@ def delete_user(user_id):
|
|||||||
Returns:
|
Returns:
|
||||||
Response: Redirect to users list with status message.
|
Response: Redirect to users list with status message.
|
||||||
"""
|
"""
|
||||||
if current_user.role != 'president':
|
if current_user.role != 'admin':
|
||||||
flash('Only the president can delete users.', 'danger')
|
flash('Only the president can delete users.', 'danger')
|
||||||
return redirect(url_for('main.dashboard'))
|
return redirect(url_for('main.dashboard'))
|
||||||
|
|
||||||
@@ -229,7 +229,7 @@ def create_user():
|
|||||||
Returns:
|
Returns:
|
||||||
Response: Create form or redirect to users list.
|
Response: Create form or redirect to users list.
|
||||||
"""
|
"""
|
||||||
if current_user.role != 'president':
|
if current_user.role != 'admin':
|
||||||
flash('Only the president can create users.', 'danger')
|
flash('Only the president can create users.', 'danger')
|
||||||
return redirect(url_for('main.dashboard'))
|
return redirect(url_for('main.dashboard'))
|
||||||
|
|
||||||
@@ -588,7 +588,7 @@ def can_manage_player_contract(user, player_id):
|
|||||||
bool: True if user has permission to manage the contract.
|
bool: True if user has permission to manage the contract.
|
||||||
"""
|
"""
|
||||||
# President can manage all contracts
|
# President can manage all contracts
|
||||||
if user.role == 'president':
|
if user.role == 'admin':
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Manager can upload contracts for any player
|
# Manager can upload contracts for any player
|
||||||
@@ -621,7 +621,7 @@ def list_contracts():
|
|||||||
if current_user.role == 'player':
|
if current_user.role == 'player':
|
||||||
# Players see their own contracts
|
# Players see their own contracts
|
||||||
contracts = Contract.query.filter_by(player_id=current_user.id).order_by(Contract.uploaded_at.desc()).all()
|
contracts = Contract.query.filter_by(player_id=current_user.id).order_by(Contract.uploaded_at.desc()).all()
|
||||||
elif current_user.role in ['president', 'manager', 'coach']:
|
elif current_user.role in ['admin', 'manager', 'coach']:
|
||||||
# Superiors see contracts for players on their teams
|
# Superiors see contracts for players on their teams
|
||||||
players = []
|
players = []
|
||||||
if current_user.role == 'coach':
|
if current_user.role == 'coach':
|
||||||
@@ -636,7 +636,7 @@ def list_contracts():
|
|||||||
player_ids = [p.id for p in players]
|
player_ids = [p.id for p in players]
|
||||||
contracts = Contract.query.filter(Contract.player_id.in_(player_ids)).order_by(Contract.uploaded_at.desc()).all()
|
contracts = Contract.query.filter(Contract.player_id.in_(player_ids)).order_by(Contract.uploaded_at.desc()).all()
|
||||||
|
|
||||||
return render_template('pages/contracts.html', contracts=contracts, players=players if current_user.role in ['president', 'manager', 'coach'] else None)
|
return render_template('pages/contracts.html', contracts=contracts, players=players if current_user.role in ['admin', 'manager', 'coach'] else None)
|
||||||
|
|
||||||
|
|
||||||
@users_bp.route('/contracts/upload', methods=['GET', 'POST'])
|
@users_bp.route('/contracts/upload', methods=['GET', 'POST'])
|
||||||
@@ -650,7 +650,7 @@ def upload_contract():
|
|||||||
Returns:
|
Returns:
|
||||||
Response: Upload form or redirect to contracts list.
|
Response: Upload form or redirect to contracts list.
|
||||||
"""
|
"""
|
||||||
if current_user.role not in ['president', 'manager', 'coach']:
|
if current_user.role not in ['admin', 'manager', 'coach']:
|
||||||
flash('Only presidents, managers, and coaches can upload contracts.', 'danger')
|
flash('Only presidents, managers, and coaches can upload contracts.', 'danger')
|
||||||
return redirect(url_for('users.list_contracts'))
|
return redirect(url_for('users.list_contracts'))
|
||||||
|
|
||||||
@@ -1395,7 +1395,7 @@ def add_personal_note():
|
|||||||
Returns:
|
Returns:
|
||||||
Response: Create form or redirect to notes view.
|
Response: Create form or redirect to notes view.
|
||||||
"""
|
"""
|
||||||
if current_user.role not in ['coach', 'manager', 'president']:
|
if current_user.role not in ['coach', 'manager', 'admin']:
|
||||||
flash('Only coaches and managers can add notes.', 'danger')
|
flash('Only coaches and managers can add notes.', 'danger')
|
||||||
return redirect(url_for('main.dashboard'))
|
return redirect(url_for('main.dashboard'))
|
||||||
|
|
||||||
@@ -1407,7 +1407,7 @@ def add_personal_note():
|
|||||||
if org_team:
|
if org_team:
|
||||||
player_ids = [tp.player_id for tp in TeamPlayer.query.filter_by(org_team_id=org_team.id).all()]
|
player_ids = [tp.player_id for tp in TeamPlayer.query.filter_by(org_team_id=org_team.id).all()]
|
||||||
players = User.query.filter(User.id.in_(player_ids)).order_by(User.username).all() if player_ids else []
|
players = User.query.filter(User.id.in_(player_ids)).order_by(User.username).all() if player_ids else []
|
||||||
elif current_user.role in ['president', 'manager']:
|
elif current_user.role in ['admin', 'manager']:
|
||||||
players = User.query.filter_by(role='player').order_by(User.username).all()
|
players = User.query.filter_by(role='player').order_by(User.username).all()
|
||||||
|
|
||||||
# Get available matches and tryouts for context
|
# Get available matches and tryouts for context
|
||||||
@@ -1417,7 +1417,7 @@ def add_personal_note():
|
|||||||
if current_user.role == 'coach' and org_team:
|
if current_user.role == 'coach' and org_team:
|
||||||
tryouts = Tryout.query.filter_by(target_org_team_id=org_team.id).order_by(Tryout.date.desc()).all()
|
tryouts = Tryout.query.filter_by(target_org_team_id=org_team.id).order_by(Tryout.date.desc()).all()
|
||||||
matches = Match.query.join(Tryout).filter(Tryout.target_org_team_id == org_team.id).order_by(Match.date.desc()).all()
|
matches = Match.query.join(Tryout).filter(Tryout.target_org_team_id == org_team.id).order_by(Match.date.desc()).all()
|
||||||
elif current_user.role in ['president', 'manager']:
|
elif current_user.role in ['admin', 'manager']:
|
||||||
tryouts = Tryout.query.order_by(Tryout.date.desc()).all()
|
tryouts = Tryout.query.order_by(Tryout.date.desc()).all()
|
||||||
matches = Match.query.order_by(Match.date.desc()).all()
|
matches = Match.query.order_by(Match.date.desc()).all()
|
||||||
teams = Team.query.order_by(Team.name).all()
|
teams = Team.query.order_by(Team.name).all()
|
||||||
@@ -1501,7 +1501,7 @@ def add_note_from_match(match_id):
|
|||||||
Returns:
|
Returns:
|
||||||
Response: Create form or redirect.
|
Response: Create form or redirect.
|
||||||
"""
|
"""
|
||||||
if current_user.role not in ['coach', 'manager', 'president']:
|
if current_user.role not in ['coach', 'manager', 'admin']:
|
||||||
flash('Only coaches can add notes from matches.', 'danger')
|
flash('Only coaches can add notes from matches.', 'danger')
|
||||||
return redirect(url_for('main.dashboard'))
|
return redirect(url_for('main.dashboard'))
|
||||||
|
|
||||||
@@ -1577,7 +1577,7 @@ def add_note_from_tryout(tryout_id):
|
|||||||
Returns:
|
Returns:
|
||||||
Response: Create form or redirect.
|
Response: Create form or redirect.
|
||||||
"""
|
"""
|
||||||
if current_user.role not in ['coach', 'manager', 'president']:
|
if current_user.role not in ['coach', 'manager', 'admin']:
|
||||||
flash('Only coaches can add notes from tryouts.', 'danger')
|
flash('Only coaches can add notes from tryouts.', 'danger')
|
||||||
return redirect(url_for('main.dashboard'))
|
return redirect(url_for('main.dashboard'))
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ def seed_database():
|
|||||||
|
|
||||||
# Create users with different roles
|
# Create users with different roles
|
||||||
users_data = [
|
users_data = [
|
||||||
{'username': 'president', 'password': 'password', 'role': 'president', 'full_name': 'Sarah Johnson', 'email': '[email protected]', 'phone': '555-0101'},
|
{'username': 'admin', 'password': 'password', 'role': 'admin', 'full_name': 'Sarah Johnson', 'email': '[email protected]', 'phone': '555-0101'},
|
||||||
{'username': 'manager1', 'password': 'password', 'role': 'manager', 'full_name': 'Mike Williams', 'email': '[email protected]', 'phone': '555-0102'},
|
{'username': 'manager1', 'password': 'password', 'role': 'manager', 'full_name': 'Mike Williams', 'email': '[email protected]', 'phone': '555-0102'},
|
||||||
{'username': 'manager2', 'password': 'password', 'role': 'manager', 'full_name': 'Emily Davis', 'email': '[email protected]', 'phone': '555-0103'},
|
{'username': 'manager2', 'password': 'password', 'role': 'manager', 'full_name': 'Emily Davis', 'email': '[email protected]', 'phone': '555-0103'},
|
||||||
{'username': 'coach1', 'password': 'password', 'role': 'coach', 'full_name': 'Coach Thompson', 'email': '[email protected]', 'phone': '555-0104', 'discord_user_id': '484107446298738689'},
|
{'username': 'coach1', 'password': 'password', 'role': 'coach', 'full_name': 'Coach Thompson', 'email': '[email protected]', 'phone': '555-0104', 'discord_user_id': '484107446298738689'},
|
||||||
@@ -117,7 +117,7 @@ def seed_database():
|
|||||||
|
|
||||||
# Map users for easy access
|
# Map users for easy access
|
||||||
user_map = {u.username: u for u in users}
|
user_map = {u.username: u for u in users}
|
||||||
president = user_map['president']
|
president = user_map['admin']
|
||||||
manager1 = user_map['manager1']
|
manager1 = user_map['manager1']
|
||||||
manager2 = user_map['manager2']
|
manager2 = user_map['manager2']
|
||||||
coaches = [user_map['coach1'], user_map['coach2'], user_map['coach3']]
|
coaches = [user_map['coach1'], user_map['coach2'], user_map['coach3']]
|
||||||
@@ -507,7 +507,7 @@ def seed_database():
|
|||||||
|
|
||||||
print("\n[SUCCESS] Database seeded successfully!")
|
print("\n[SUCCESS] Database seeded successfully!")
|
||||||
print("\n=== Login Credentials ===")
|
print("\n=== Login Credentials ===")
|
||||||
print("President: username='president', password='password'")
|
print("President: username='admin', password='password'")
|
||||||
print("Manager: username='manager1', password='password'")
|
print("Manager: username='manager1', password='password'")
|
||||||
print("Coach: username='coach1', password='password' (assigned to Varsity)")
|
print("Coach: username='coach1', password='password' (assigned to Varsity)")
|
||||||
print("Coach: username='coach2', password='password' (assigned to Junior Varsity)")
|
print("Coach: username='coach2', password='password' (assigned to Junior Varsity)")
|
||||||
|
|||||||
@@ -1231,9 +1231,9 @@ a:hover { color: var(--primary-dark); }
|
|||||||
}
|
}
|
||||||
|
|
||||||
.merged-disponibility-time-block.selected {
|
.merged-disponibility-time-block.selected {
|
||||||
background: var(--success);
|
background: #6366f1 !important;
|
||||||
color: white;
|
color: white !important;
|
||||||
border-color: var(--success);
|
border-color: #4f46e5 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.merged-disponibility-time-block.high-availability {
|
.merged-disponibility-time-block.high-availability {
|
||||||
@@ -1266,10 +1266,10 @@ a:hover { color: var(--primary-dark); }
|
|||||||
|
|
||||||
/* Then specific states override the generic rule */
|
/* Then specific states override the generic rule */
|
||||||
[data-theme="dark"] .merged-disponibility-time-block.selected {
|
[data-theme="dark"] .merged-disponibility-time-block.selected {
|
||||||
background: var(--success) !important;
|
background: #6366f1 !important;
|
||||||
color: white !important;
|
color: white !important;
|
||||||
border-color: var(--success) !important;
|
border-color: #818cf8 !important;
|
||||||
box-shadow: 0 0 0 2px rgba(16, 185, 129, 0.5);
|
box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.5);
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="dark"] .merged-disponibility-time-block.high-availability {
|
[data-theme="dark"] .merged-disponibility-time-block.high-availability {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
{% block breadcrumb %}<span class="breadcrumb">Home / <a href="{{ url_for('users.profile') }}">Profile</a> / Contracts</span>{% endblock %}
|
{% block breadcrumb %}<span class="breadcrumb">Home / <a href="{{ url_for('users.profile') }}">Profile</a> / Contracts</span>{% endblock %}
|
||||||
|
|
||||||
{% block header_actions %}
|
{% 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">
|
<a href="{{ url_for('users.upload_contract') }}" class="btn btn-primary">
|
||||||
<i class="fas fa-upload"></i> Upload Contract
|
<i class="fas fa-upload"></i> Upload Contract
|
||||||
</a>
|
</a>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="dashboard">
|
<div class="dashboard">
|
||||||
{% if user.role == 'president' %}
|
{% if user.role == 'admin' %}
|
||||||
<div class="stats-grid">
|
<div class="stats-grid">
|
||||||
<div class="stat-card">
|
<div class="stat-card">
|
||||||
<div class="stat-icon bg-primary">
|
<div class="stat-icon bg-primary">
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
</th>
|
</th>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
{% if current_user.role == 'president' and player_scores %}
|
{% if current_user.role == 'admin' and player_scores %}
|
||||||
<div class="stats-grid mb-4">
|
<div class="stats-grid mb-4">
|
||||||
{% for pid, data in player_scores.items() %}
|
{% for pid, data in player_scores.items() %}
|
||||||
<div class="stat-card stat-card-sm">
|
<div class="stat-card stat-card-sm">
|
||||||
|
|||||||
@@ -75,7 +75,12 @@
|
|||||||
{% if current_user.can_manage_teams() or current_user.can_schedule_matches() %}
|
{% if current_user.can_manage_teams() or current_user.can_schedule_matches() %}
|
||||||
<hr class="section-divider">
|
<hr class="section-divider">
|
||||||
<h4 class="section-title"><i class="fas fa-clock"></i> Select Match Time</h4>
|
<h4 class="section-title"><i class="fas fa-clock"></i> Select Match Time</h4>
|
||||||
<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">
|
<div id="merged-disponibility-grid" class="merged-disponibility-grid">
|
||||||
<p class="text-muted">Loading...</p>
|
<p class="text-muted">Loading...</p>
|
||||||
|
|||||||
@@ -77,12 +77,11 @@
|
|||||||
<!-- Player presence details -->
|
<!-- Player presence details -->
|
||||||
<div class="presence-players">
|
<div class="presence-players">
|
||||||
{% for p in item.participants %}
|
{% 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 %}"
|
<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 %}"
|
title="{{ p.player.username }}{% if p.is_confirmed %} - Confirmed{% else %} - Pending{% endif %}">
|
||||||
style="text-decoration: none;">
|
|
||||||
{{ p.player.username[:2] | upper }} {{ p.player.username }}
|
{{ p.player.username[:2] | upper }} {{ p.player.username }}
|
||||||
{% if p.is_confirmed %}✅{% else %}⏳{% endif %}
|
{% if p.is_confirmed %}✅{% else %}⏳{% endif %}
|
||||||
</a>
|
</span>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
{% else %}
|
{% else %}
|
||||||
@@ -93,7 +92,7 @@
|
|||||||
<span class="badge badge-{{ m.status }}">{{ m.status }}</span>
|
<span class="badge badge-{{ m.status }}">{{ m.status }}</span>
|
||||||
</td>
|
</td>
|
||||||
<td class="eval-actions">
|
<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 %}
|
{% if can_manage_this %}
|
||||||
<a href="{{ url_for('team_matches.edit_match', match_id=m.id) }}" class="btn btn-sm btn-outline" title="Edit Match">
|
<a href="{{ url_for('team_matches.edit_match', match_id=m.id) }}" class="btn btn-sm btn-outline" title="Edit Match">
|
||||||
<i class="fas fa-edit"></i>
|
<i class="fas fa-edit"></i>
|
||||||
|
|||||||
+70
-47
@@ -124,36 +124,10 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
</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">
|
<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
|
<i class="fas fa-futbol"></i> Matches
|
||||||
</a>
|
</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">
|
<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
|
<i class="fas fa-plus"></i> Match
|
||||||
</a>
|
</a>
|
||||||
@@ -286,28 +260,37 @@
|
|||||||
<div class="modal-body">
|
<div class="modal-body">
|
||||||
<form id="editTeamForm" method="POST" action="" class="form">
|
<form id="editTeamForm" method="POST" action="" class="form">
|
||||||
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||||
|
<input type="hidden" name="sync_staff" value="1"/>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="edit_name">Team Name</label>
|
<label for="edit_name">Team Name</label>
|
||||||
<input type="text" id="edit_name" name="name" required>
|
<input type="text" id="edit_name" name="name" required>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<hr class="section-divider">
|
||||||
|
|
||||||
|
<!-- Manage Coaches -->
|
||||||
|
<h5 class="mb-2"><i class="fas fa-chalkboard-teacher"></i> Coaches</h5>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="edit_coach_id">Assigned Coach</label>
|
<select name="coach_ids" id="edit-coach-select" class="form-select" multiple style="min-height: 100px; width: 100%;">
|
||||||
<select id="edit_coach_id" name="coach_id" class="form-select">
|
|
||||||
<option value="">-- No coach assigned --</option>
|
|
||||||
{% for coach in coaches %}
|
{% for coach in coaches %}
|
||||||
<option value="{{ coach.id }}">{{ coach.username }}</option>
|
<option value="{{ coach.id }}" class="coach-option">{{ coach.username }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
<small class="form-text">Hold Ctrl/Cmd to select multiple. Only unassigned coaches shown.</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Manage Managers -->
|
||||||
|
<h5 class="mb-2"><i class="fas fa-user-tie"></i> Managers</h5>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="edit_manager_id">Assigned Manager</label>
|
<select name="manager_ids" id="edit-manager-select" class="form-select" multiple style="min-height: 100px; width: 100%;">
|
||||||
<select id="edit_manager_id" name="manager_id" class="form-select">
|
|
||||||
<option value="">-- No manager assigned --</option>
|
|
||||||
{% for manager in managers %}
|
{% for manager in managers %}
|
||||||
<option value="{{ manager.id }}">{{ manager.username }}</option>
|
<option value="{{ manager.id }}" class="manager-option">{{ manager.username }}</option>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</select>
|
</select>
|
||||||
|
<small class="form-text">Hold Ctrl/Cmd to select multiple. Only unassigned managers shown.</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
<button type="button" class="btn btn-secondary" onclick="hideEditForm()">Cancel</button>
|
<button type="button" class="btn btn-secondary" onclick="hideEditForm()">Cancel</button>
|
||||||
<button type="submit" class="btn btn-primary">Save Changes</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.addEventListener('DOMContentLoaded', function() {
|
||||||
document.querySelectorAll('.edit-team-btn').forEach(function(btn) {
|
document.querySelectorAll('.edit-team-btn').forEach(function(btn) {
|
||||||
btn.addEventListener('click', function() {
|
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 teamName = this.getAttribute('data-team-name');
|
||||||
var coachId = this.getAttribute('data-coach-id');
|
document.getElementById('editTeamForm').action = '/teams/' + currentEditTeamId + '/edit';
|
||||||
var managerId = this.getAttribute('data-manager-id');
|
|
||||||
document.getElementById('editTeamForm').action = '/teams/' + teamId + '/edit';
|
|
||||||
document.getElementById('edit_name').value = teamName;
|
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');
|
document.getElementById('editTeamModal').classList.remove('hidden');
|
||||||
|
populateEditSelects(currentEditTeamId);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
function showEditForm(teamId, teamName, coachId, managerId) {
|
function getCurrentStaffIds(teamId, type) {
|
||||||
document.getElementById('editTeamForm').action = '/teams/' + teamId + '/edit';
|
// Get currently assigned coach/manager IDs from the staff bar pills
|
||||||
document.getElementById('edit_name').value = teamName;
|
var ids = [];
|
||||||
document.getElementById('edit_coach_id').value = coachId || '';
|
var teamCard = document.querySelector('[data-team-id="' + teamId + '"]');
|
||||||
document.getElementById('edit_manager_id').value = managerId || '';
|
if (!teamCard) return ids;
|
||||||
document.getElementById('editTeamModal').classList.remove('hidden');
|
|
||||||
|
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() {
|
function hideEditForm() {
|
||||||
|
|||||||
@@ -338,6 +338,18 @@
|
|||||||
<span class="badge badge-secondary">⏳ 0/{{ item.total_count }}</span>
|
<span class="badge badge-secondary">⏳ 0/{{ item.total_count }}</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</span>
|
</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 %}
|
{% else %}
|
||||||
<span class="text-muted">—</span>
|
<span class="text-muted">—</span>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
@@ -484,10 +496,62 @@
|
|||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</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>
|
<script>
|
||||||
function showCreateTeam() { document.getElementById('createTeamForm').classList.remove('hidden'); }
|
function showCreateTeam() { document.getElementById('createTeamForm').classList.remove('hidden'); }
|
||||||
function hideCreateTeam() { document.getElementById('createTeamForm').classList.add('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
|
// Mini calendar for matches
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
var miniCalendarEl = document.getElementById('mini-calendar');
|
var miniCalendarEl = document.getElementById('mini-calendar');
|
||||||
|
|||||||
+2
-2
@@ -265,7 +265,7 @@ class CreateUserSchema(StripMixin):
|
|||||||
role = fields.String(
|
role = fields.String(
|
||||||
required=True,
|
required=True,
|
||||||
validate=validate.OneOf(
|
validate=validate.OneOf(
|
||||||
['president', 'manager', 'coach', 'player', 'scout'],
|
['admin', 'manager', 'coach', 'player', 'scout'],
|
||||||
error='Invalid role selected.'
|
error='Invalid role selected.'
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
@@ -302,7 +302,7 @@ class EditUserSchema(StripMixin):
|
|||||||
role = fields.String(
|
role = fields.String(
|
||||||
required=True,
|
required=True,
|
||||||
validate=validate.OneOf(
|
validate=validate.OneOf(
|
||||||
['president', 'manager', 'coach', 'player', 'scout'],
|
['admin', 'manager', 'coach', 'player', 'scout'],
|
||||||
error='Invalid role selected.'
|
error='Invalid role selected.'
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user