from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify from flask_login import login_required from app import db from app.models.classroom import Classroom, ClassroomResource from app.models.reservation import Reservation, ReservationStatus from app.forms.classroom import ClassroomForm, ClassroomResourceForm from datetime import datetime classrooms_bp = Blueprint('classrooms', __name__, url_prefix='/classrooms') @classrooms_bp.route('/') @login_required def list_classrooms(): """List all classrooms""" page = request.args.get('page', 1, type=int) search = request.args.get('search', '') campus = request.args.get('campus', '') query = Classroom.query.filter_by(is_active=True) if search: query = query.filter( Classroom.code.contains(search) | Classroom.name.contains(search) | Classroom.building.contains(search) ) if campus: query = query.filter(Classroom.campus_location == campus) classrooms = query.paginate( page=page, per_page=20, error_out=False ) return render_template('classrooms/list.html', classrooms=classrooms, search=search, campus=campus) @classrooms_bp.route('/add', methods=['GET', 'POST']) @login_required def add_classroom(): """Add new classroom""" form = ClassroomForm() if form.validate_on_submit(): classroom = Classroom( code=form.code.data, name=form.name.data, building=form.building.data, floor=form.floor.data, capacity=form.capacity.data, description=form.description.data, campus_location=form.campus_location.data ) try: db.session.add(classroom) db.session.commit() flash(f'Classroom {classroom.code} added successfully!', 'success') return redirect(url_for('classrooms.list_classrooms')) except Exception as e: db.session.rollback() flash('Error adding classroom. Classroom code might already exist.', 'error') return render_template('classrooms/add.html', form=form) @classrooms_bp.route('/edit/', methods=['GET', 'POST']) @login_required def edit_classroom(Id): """Edit classroom""" classroom = Classroom.query.get_or_404(Id) if not classroom.is_active: flash('This classroom has been deactivated.', 'warning') return redirect(url_for('classrooms.list_classrooms')) form = ClassroomForm(obj=classroom) if form.validate_on_submit(): try: classroom.code = form.code.data classroom.name = form.name.data classroom.building = form.building.data classroom.floor = form.floor.data classroom.capacity = form.capacity.data classroom.description = form.description.data classroom.campus_location = form.campus_location.data classroom.updated_at = datetime.utcnow() db.session.commit() flash(f'Classroom {classroom.code} updated successfully!', 'success') return redirect(url_for('classrooms.view_classroom', Id=classroom.id)) except Exception as e: db.session.rollback() flash('Error updating classroom.', 'error') return render_template('classrooms/edit.html', form=form, classroom=classroom) @classrooms_bp.route('/view/') @login_required def view_classroom(Id): """View classroom details""" classroom = Classroom.query.get_or_404(Id) if not classroom.is_active: flash('This classroom has been deactivated.', 'warning') return redirect(url_for('classrooms.list_classrooms')) # Get classroom resources resources = ClassroomResource.query.filter_by(classroom_id=Id).all() # Get upcoming reservations upcoming_reservations = Reservation.query.filter( Reservation.classroom_id == Id, Reservation.start_time >= datetime.utcnow(), Reservation.status.in_([ReservationStatus.CONFIRMED, ReservationStatus.PENDING]) ).order_by(Reservation.start_time).limit(10).all() return render_template('classrooms/view.html', classroom=classroom, resources=resources, upcoming_reservations=upcoming_reservations) @classrooms_bp.route('/delete/') @login_required def delete_classroom(Id): """Delete/deactivate classroom""" classroom = Classroom.query.get_or_404(Id) # Check for active reservations active_reservations = Reservation.query.filter( Reservation.classroom_id == Id, Reservation.status.in_([ReservationStatus.CONFIRMED, ReservationStatus.PENDING]) ).count() if active_reservations > 0: flash('Cannot delete classroom with active reservations.', 'error') return redirect(url_for('classrooms.view_classroom', Id=Id)) try: classroom.is_active = False classroom.updated_at = datetime.utcnow() db.session.commit() flash(f'Classroom {classroom.code} has been deactivated.', 'success') except Exception as e: db.session.rollback() flash('Error deactivating classroom.', 'error') return redirect(url_for('classrooms.list_classrooms')) @classrooms_bp.route('//add_resource', methods=['GET', 'POST']) @login_required def add_resource(classroom_id): """Add resource to classroom""" classroom = Classroom.query.get_or_404(classroom_id) form = ClassroomResourceForm() if form.validate_on_submit(): resource = ClassroomResource( classroom_id=classroom_id, resource_type=form.resource_type.data, brand=form.brand.data, model=form.model.data, specifications=form.specifications.data, quantity=form.quantity.data ) try: db.session.add(resource) db.session.commit() flash(f'{resource.resource_type} added to {classroom.code} successfully!', 'success') return redirect(url_for('classrooms.view_classroom', Id=classroom_id)) except Exception as e: db.session.rollback() flash('Error adding resource.', 'error') return render_template('classrooms/add_resource.html', form=form, classroom=classroom) @classrooms_bp.route('/api/available') @login_required def api_available_classrooms(): """API endpoint to get available classrooms""" start_time = request.args.get('start_time') end_time = request.args.get('end_time') min_capacity = request.args.get('min_capacity', 0, type=int) if not start_time or not end_time: return jsonify({'error': 'start_time and end_time are required'}), 400 try: start_dt = datetime.fromisoformat(start_time) end_dt = datetime.fromisoformat(end_time) except ValueError: return jsonify({'error': 'Invalid datetime format'}), 400 # Find available classrooms (without conflicts) conflicting_classroom_ids = [ r.classroom_id for r in Reservation.query.filter( Reservation.start_time < end_dt, Reservation.end_time > start_dt, Reservation.status == ReservationStatus.CONFIRMED ).all() ] available_classrooms = Classroom.query.filter( Classroom.is_active == True, Classroom.capacity >= min_capacity, ~Classroom.id.in_(conflicting_classroom_ids) ).all() return jsonify([classroom.to_dict() for classroom in available_classrooms])