"""One-on-one sessions between a player and their coach.""" from datetime import datetime from flask import flash, redirect, render_template, request, url_for from flask_babel import gettext as _ from flask_login import current_user, login_required from app.extensions import db from app.models import Coach, CoachAvailability, OneOnOneRequest, PersonalNote, Player, TeamNote from app.routes.users.blueprint import users_bp from app.services.notifications import send_discord_notification @users_bp.route('/one-on-one', methods=['GET', 'POST']) @login_required def one_on_one(): """One on One request page for players.""" if not isinstance(current_user, Player): flash(_('Only players can request One on One sessions.'), 'danger') return redirect(url_for('main.dashboard')) org_teams = current_user.get_org_teams() org_team = org_teams[0] if org_teams else None # Reading org_team.coach_id directly told every player whose team lists # its coaches through the many-to-many relationship — the newer of the # two ways — that they had no coach, and closed the page to them. # get_coaches() falls back to the legacy column when the list is empty. team_coaches = org_team.get_coaches() if org_team else [] coach = team_coaches[0] if team_coaches else None if not coach: flash(_('You do not have a coach assigned to your team.'), 'info') team_notes = [] if org_team: team_notes = ( TeamNote.query.filter_by(org_team_id=org_team.id) .order_by(TeamNote.created_at.desc()) .all() ) personal_notes = ( PersonalNote.query.filter_by(player_id=current_user.id) .order_by(PersonalNote.created_at.desc()) .all() ) coach_availability = [] if coach: availabilities = CoachAvailability.query.filter_by(coach_id=coach.id).all() coach_availability = [ { 'day_of_week': av.day_of_week, 'start_time': av.start_time.strftime('%H:%M'), 'end_time': av.end_time.strftime('%H:%M'), } for av in availabilities ] if request.method == 'POST': date_str = request.form.get('date') start_time_str = request.form.get('start_time') end_time_str = request.form.get('end_time') points = request.form.get('points', '').strip() if not coach: flash(_('Cannot request One on One - no coach assigned.'), 'danger') return redirect(url_for('users.one_on_one')) try: date_obj = datetime.strptime(date_str, '%Y-%m-%d').date() start_time = datetime.strptime(start_time_str, '%H:%M').time() end_time = datetime.strptime(end_time_str, '%H:%M').time() except (ValueError, TypeError): flash(_('Invalid date or time format.'), 'danger') return redirect(url_for('users.one_on_one')) check_date = datetime.strptime(date_str, '%Y-%m-%d') day_of_week = check_date.weekday() is_available = any( av['day_of_week'] == day_of_week and av['start_time'] <= start_time_str and av['end_time'] >= end_time_str for av in coach_availability ) if not is_available: flash(_("The requested time is not within the coach's availability."), 'danger') return redirect(url_for('users.one_on_one')) request_obj = OneOnOneRequest( player_id=current_user.id, coach_id=coach.id, org_team_id=org_team.id if org_team else None, date=date_obj, start_time=start_time, end_time=end_time, points=points if points else None, ) db.session.add(request_obj) db.session.commit() send_discord_notification( player_name=current_user.full_name, points=points, date_str=date_str, start_time_str=start_time_str, end_time_str=end_time_str, team_name=org_team.name if org_team else 'Unknown Team', coach_name=coach.full_name, coach_discord=coach.discord_username or '', coach_discord_id=coach.discord_user_id or '', request_id=request_obj.id, ) flash(_('Your One on One request has been submitted!'), 'success') return redirect(url_for('users.one_on_one')) # Build list of upcoming dates that have coach availability from datetime import date as date_cls from datetime import timedelta as td today = date_cls.today() available_days = {av['day_of_week'] for av in coach_availability} dates = [] for i in range(14): # Next 14 days d = today + td(days=i) if d.weekday() in available_days: dates.append( { 'value': d.strftime('%Y-%m-%d'), 'day_of_week': d.weekday(), 'display': d.strftime('%B %d, %Y (%A)'), } ) # Player's own One on One request history my_requests = ( OneOnOneRequest.query.filter_by(player_id=current_user.id) .order_by(OneOnOneRequest.created_at.desc()) .all() ) return render_template( 'pages/one_on_one.html', org_team=org_team, coach=coach, team_notes=team_notes, personal_notes=personal_notes, coach_availability=coach_availability, dates=dates, my_requests=my_requests, ) @users_bp.route('/one-on-one//accept', methods=['POST']) @login_required def accept_one_on_one(request_id): """Coach accepts a One on One request.""" if not isinstance(current_user, Coach): flash(_('Only coaches can accept One on One requests.'), 'danger') return redirect(url_for('main.dashboard')) request_obj = OneOnOneRequest.query.get_or_404(request_id) if request_obj.coach_id != current_user.id: flash(_('This request is not for you.'), 'danger') return redirect(url_for('users.notes_dashboard')) if request_obj.status != 'pending': flash(_('This request has already been processed.'), 'info') return redirect(url_for('users.notes_dashboard')) player = request_obj.player request_obj.status = 'approved' request_obj.responded_at = datetime.utcnow() db.session.commit() # Notify player via Discord (same message as if approved through Discord reactions) if player and player.discord_user_id: from app.discord_bot import send_one_on_one_response send_one_on_one_response( player_discord_id=player.discord_user_id, player_full_name=player.full_name, coach_full_name=current_user.full_name, date_str=request_obj.date.strftime('%A, %B %d, %Y'), start_time=request_obj.start_time.strftime('%I:%M %p') if request_obj.start_time else 'TBD', end_time=request_obj.end_time.strftime('%I:%M %p') if request_obj.end_time else 'TBD', points=request_obj.points or 'No specific points provided', approved=True, ) flash( _( 'One on One request from %(player)s has been approved!', player=player.username if player else 'Unknown', ), 'success', ) return redirect(url_for('users.notes_dashboard')) @users_bp.route('/one-on-one//reject', methods=['POST']) @login_required def reject_one_on_one(request_id): """Coach rejects a One on One request.""" if not isinstance(current_user, Coach): flash(_('Only coaches can reject One on One requests.'), 'danger') return redirect(url_for('main.dashboard')) request_obj = OneOnOneRequest.query.get_or_404(request_id) if request_obj.coach_id != current_user.id: flash(_('This request is not for you.'), 'danger') return redirect(url_for('users.notes_dashboard')) if request_obj.status != 'pending': flash(_('This request has already been processed.'), 'info') return redirect(url_for('users.notes_dashboard')) rejection_reason = request.form.get('rejection_reason', '').strip() player = request_obj.player request_obj.status = 'rejected' request_obj.responded_at = datetime.utcnow() if rejection_reason: request_obj.coach_rejection_message = rejection_reason db.session.commit() # Notify player via Discord (same message as if rejected through Discord reactions) if player and player.discord_user_id: from app.discord_bot import send_one_on_one_response send_one_on_one_response( player_discord_id=player.discord_user_id, player_full_name=player.full_name, coach_full_name=current_user.full_name, date_str=request_obj.date.strftime('%A, %B %d, %Y'), start_time=request_obj.start_time.strftime('%I:%M %p') if request_obj.start_time else 'TBD', end_time=request_obj.end_time.strftime('%I:%M %p') if request_obj.end_time else 'TBD', points=request_obj.points or 'No specific points provided', approved=False, refusal_note=rejection_reason or None, ) flash( _( 'One on One request from %(player)s has been rejected.', player=player.username if player else 'Unknown', ), 'info', ) return redirect(url_for('users.notes_dashboard'))