from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify from flask_login import login_required, current_user from app import db from app.models.reservation import Reservation, ReservationStatus from app.models.classroom import Classroom from app.models.subject import Subject, Commission from app.forms.reservation import ReservationForm from datetime import datetime, timedelta, date from sqlalchemy import func, or_ from sqlalchemy.orm import joinedload schedule_bp = Blueprint('schedule', __name__, url_prefix='/schedule') @schedule_bp.route('/today') @login_required def today_schedule(): """Show today's schedule""" today = datetime.utcnow().date() today_start = datetime.combine(today, datetime.min.time()) today_end = datetime.combine(today, datetime.max.time()) # Get reservations with eager loading reservations = Reservation.query.options( joinedload(Reservation.classroom), joinedload(Reservation.commission), joinedload(Reservation.user) ).filter( Reservation.start_time >= today_start, Reservation.start_time <= today_end, Reservation.status.in_([ReservationStatus.CONFIRMED, ReservationStatus.PENDING]) ).order_by(Reservation.start_time).all() # Create time blocks for timeline view time_blocks = {} for reservation in reservations: start_hour = reservation.start_time.hour end_hour = reservation.end_time.hour for hour in range(max(7, start_hour), min(22, end_hour + 1)): if hour not in time_blocks: time_blocks[hour] = [] if reservation not in time_blocks[hour]: time_blocks[hour].append({ 'reservation': reservation, 'classroom': reservation.classroom, 'commission': reservation.commission, 'user': reservation.user }) # Calculate stats total_reservations = len(reservations) confirmed = len([r for r in reservations if r.status == ReservationStatus.CONFIRMED]) pending = len([r for r in reservations if r.status == ReservationStatus.PENDING]) # Get total classrooms and calculate occupied ones from app.models.classroom import Classroom total_classrooms = Classroom.query.count() occupied_classrooms = len(set(r.classroom_id for r in reservations if r.classroom_id)) stats = { 'total_reservations': total_reservations, 'confirmed': confirmed, 'pending': pending, 'occupied_classrooms': occupied_classrooms, 'total_classrooms': total_classrooms } return render_template('schedule/today.html', reservations=reservations, time_blocks=time_blocks, stats=stats, today=today) @schedule_bp.route('/cancel/', methods=['POST']) @login_required def cancel_reservation(id): """Cancel a reservation""" try: reservation = Reservation.query.get_or_404(id) if not reservation.can_be_cancelled(): flash('Reservations can only be cancelled at least 24 hours before start time.', 'error') return redirect(request.referrer) reservation.status = ReservationStatus.CANCELLED db.session.commit() flash('Reservation cancelled successfully!', 'success') except Exception as e: db.session.rollback() flash(f'Error cancelling reservation: {str(e)}', 'error') return redirect(request.referrer) @schedule_bp.route('/reservation//view') @login_required def view_reservation(id): """View reservation details""" try: reservation = Reservation.query.options( joinedload(Reservation.classroom), joinedload(Reservation.commission), joinedload(Reservation.user) ).get_or_404(id) return render_template('schedule/view.html', reservation=reservation, title='Reservation Details') except Exception as e: flash(f'Error loading reservation details: {str(e)}', 'error') return redirect(url_for('schedule.today_schedule')) @schedule_bp.route('/calendar') @login_required def calendar_view(): """Show calendar view""" return render_template('schedule/calendar.html', title='Calendar View') @schedule_bp.route('/add') @login_required def add_reservation(): """Add new reservation""" form = ReservationForm() return render_template('schedule/create.html', form=form, title='New Reservation') @schedule_bp.route('/list') @login_required def list_reservations(): """List all reservations""" return redirect(url_for('schedule.today_schedule')) @schedule_bp.route('/reservation//confirm', methods=['POST']) @login_required def confirm_reservation(id): """Confirm a reservation""" try: reservation = Reservation.query.get_or_404(id) if reservation.status != ReservationStatus.PENDING: flash('Only pending reservations can be confirmed.', 'error') return redirect(request.referrer) reservation.status = ReservationStatus.CONFIRMED db.session.commit() flash('Reservation confirmed successfully!', 'success') except Exception as e: db.session.rollback() flash(f'Error confirming reservation: {str(e)}', 'error') return redirect(request.referrer) # API endpoints @schedule_bp.route('/calendar-data') @login_required def calendar_data(): """Get calendar events data for FullCalendar""" try: # Get date range from query parameters start = request.args.get('start') end = request.args.get('end') if start and end: from datetime import datetime start_date = datetime.fromisoformat(start) end_date = datetime.fromisoformat(end) else: # Default to current month today = date.today() start_date = datetime.combine(today.replace(day=1), datetime.min.time()) next_month = today.replace(day=28) + timedelta(days=4) # Next month end_date = datetime.combine(next_month.replace(day=1), datetime.max.time()) # Get reservations in the date range reservations = Reservation.query.options( joinedload(Reservation.classroom), joinedload(Reservation.commission) ).filter( Reservation.start_time >= start_date, Reservation.start_time <= end_date, Reservation.status.in_([ReservationStatus.CONFIRMED, ReservationStatus.PENDING]) ).order_by(Reservation.start_time).all() events = [] for reservation in reservations: events.append({ 'id': reservation.id, 'title': f"{reservation.commission.get_full_code() if reservation.commission else 'No Commission'} - {reservation.classroom.code_display}", 'start': reservation.start_time.isoformat(), 'end': reservation.end_time.isoformat(), 'url': url_for('schedule.view_reservation', id=reservation.id) if reservation.id else None, 'className': f'fc-event-{reservation.status.value.lower()}', 'extendedProps': { 'classroom': reservation.classroom.code_display if reservation.classroom else 'Unknown', 'commission': reservation.commission.get_full_code() if reservation.commission else 'No Commission', 'purpose': reservation.purpose or 'No purpose', 'attendees': reservation.expected_attendees, 'status': reservation.status.value, 'notes': reservation.notes or '' } }) return jsonify(events) except Exception as e: return jsonify({'error': str(e)}), 500 @schedule_bp.route('/api/today-events') @login_required def api_today_events(): """Get today's events as JSON""" try: today_start = datetime.combine(date.today(), datetime.min.time()) today_end = datetime.combine(date.today(), datetime.max.time()) reservations = Reservation.query.options( joinedload(Reservation.classroom), joinedload(Reservation.commission) ).filter( Reservation.start_time >= today_start, Reservation.start_time <= today_end, Reservation.status == ReservationStatus.CONFIRMED ).order_by(Reservation.start_time).all() events = [] for reservation in reservations: events.append({ 'id': reservation.id, 'title': f"{reservation.commission.get_full_code() if reservation.commission else 'No Commission'} - {reservation.classroom.code_display}", 'start': reservation.start_time.isoformat(), 'end': reservation.end_time.isoformat(), 'url': url_for('schedule.view_reservation', id=reservation.id) if reservation.id else None, 'extendedProps': { 'classroom': reservation.classroom.code_display if reservation.classroom else 'Unknown', 'purpose': reservation.purpose or 'No purpose', 'attendees': reservation.expected_attendees, 'status': reservation.status.value } }) return jsonify({'events': events}) except Exception as e: return jsonify({'error': str(e)}), 500