Help me do these changes for scheduleview.vue:
1. Ensure the calendar occupies 70% and right card occupies 30% of screen when they are side by side
2. For the details displayed in the right card, Make the collapsible look nicer and aestetically pleasing, by making them have borders, like a card. 
3. Each time block displayed in the right card should have a total count of people in office / total number of people in department
4. For each staff listed in the right card, make them a separate card or list component that has borders, so it looks nicer

Code context:
Directory: backend\app
--------------------------------------------------------------------------------

Directory: backend\app\controllers
--------------------------------------------------------------------------------

File: backend\app\controllers\staff_controller.py
--------------------------------------------------------------------------------
# app/controllers/staff_controller.py
from flask import Blueprint, request, jsonify
from flask import abort
from app.services.staff_service import StaffService

staff_bp = Blueprint('staff', __name__, url_prefix='/api')

@staff_bp.route('/login', methods=['POST'])
def login():
    data = request.get_json()

    if not data or not data.get('email') or not data.get('password'):
        return jsonify({"message": "Missing email or password"}), 400

    staff = StaffService.authenticate_staff(data['email'], data['password'])

    if not staff:
        return jsonify({"message": "Invalid email or password"}), 401
    
    staff_details = staff.to_dict()
    print(staff_details)
    return jsonify({
        "message": "Login successful",
        **staff_details
    }), 200

@staff_bp.route('/staff/<int:staff_id>', methods=['GET'])
def get_staff_by_id(staff_id):
    staff = StaffService.get_staff_by_id(staff_id)
    if not staff:
        abort(404, description="Staff not found")
    return jsonify(staff.to_dict()), 200


File: backend\app\controllers\wfh_controller.py
--------------------------------------------------------------------------------
from flask import Blueprint, request, jsonify
from app.services.wfh_request_service import WFHRequestService
from app.services.wfh_schedule_service import WFHScheduleService
from app.services.wfh_check_service import WFHCheckService
from app.models.wfh_request import WFHRequest
from datetime import datetime, timedelta, date
from app import db

wfh_bp = Blueprint('wfh', __name__, url_prefix='/api')

@wfh_bp.route('/request', methods=['POST'])
def create_wfh_request():
    print("\n===== NEW WFH REQUEST =====")
    print("Received a new WFH request")
    data = request.get_json()

    # Validate input
    print("\n----- Input Validation -----")
    required_fields = ['staff_id', 'manager_id', 'reason_for_applying', 
                       'date', 'duration', 'dept', 'position']
    for field in required_fields:
        if field not in data:
            print(f"Validation failed: Missing required field: {field}")
            return jsonify({"message": f"Missing required field: {field}"}), 400
    print("Input validation successful")

    try:
        # Get today's date for the request_date
        today = datetime.now().date()

        # Create WFHRequest
        print("\n----- Creating WFH Request -----")
        end_date = None
        if 'end_date' in data and data['end_date']:
            if(not isinstance(data['end_date'], date)):
                end_date = datetime.strptime(data['end_date'], '%Y-%m-%d').date()

        wfh_request = WFHRequestService.create_request(
            staff_id=data['staff_id'],
            manager_id=data['manager_id'],
            request_date=today,
            start_date=data['date'],
            end_date=end_date,
            reason_for_applying=data['reason_for_applying']
        )
        print(f"WFH request created successfully. Request ID: {wfh_request.request_id}")

        # Create WFH Schedules
        print("\n----- Creating WFH Schedules -----")
        if(not isinstance(data['date'], date)):
            start_date = datetime.strptime(data['date'], '%Y-%m-%d').date()
        wfh_schedules = WFHScheduleService.create_schedule(
            request_id=wfh_request.request_id,
            staff_id=data['staff_id'],
            manager_id=data['manager_id'],
            start_date=start_date,
            end_date=end_date,
            duration=data['duration'],
            dept=data['dept'],
            position=data['position']
        )
        print(f"WFH schedules created successfully. Number of schedules: {len(wfh_schedules)}")

        print("\n===== WFH REQUEST COMPLETED =====")
        print("WFH request and schedules creation completed successfully")
        print(f"Request ID: {wfh_request.request_id}")
        print(f"Number of Schedules: {len(wfh_schedules)}")
        print(f"Status: {wfh_request.status}")
        print("==================================\n")

        return jsonify({
            "message": "WFH request and schedules created successfully",
            "request_id": wfh_request.request_id,
            "schedule_count": len(wfh_schedules),
            "status": wfh_request.status
        }), 201

    except ValueError as ve:
        print(f"\n===== ERROR OCCURRED =====")
        print(f"Validation failed: {ve}")
        print("============================\n")
        db.session.rollback()
        return jsonify({"message": str(ve)}), 400
        
    except Exception as e:
        db.session.rollback()
        print("\n===== ERROR OCCURRED =====")
        print(f"An error occurred while processing the WFH request: {str(e)}")
        print("============================\n")
        db.session.rollback()
        return jsonify({"message": f"An error occurred: {str(e)}"}), 500

@wfh_bp.route('/pending-requests/<int:manager_id>', methods=['GET'])
def get_pending_requests(manager_id):
    print(f"\n===== GET PENDING REQUESTS =====")
    print(f"Retrieving pending requests for manager_id: {manager_id}")
    
    try:
        print("Calling WFHRequestService.get_pending_requests_for_manager()")
        pending_requests = WFHRequestService.get_pending_requests_for_manager(manager_id)
        
        print(f"Number of pending requests retrieved: {len(pending_requests)}")
        
        response = [request.to_dict() for request in pending_requests]
        print("Successfully converted requests to dictionary format")
        
        print("===== GET PENDING REQUESTS COMPLETED =====\n")
        return jsonify(response), 200
    
    except Exception as e:
        print(f"ERROR: An exception occurred while retrieving pending requests")
        print(f"Exception details: {str(e)}")
        print("===== GET PENDING REQUESTS FAILED =====\n")
        return jsonify({"message": f"An error occurred: {str(e)}"}), 500


@wfh_bp.route('/update-request', methods=['PATCH'])
def update_wfh_request():
    print(f"\n===== UPDATE REQUESTS =====")
    data = request.get_json()
    request_id = data['request_id']
    new_request_status = data['request_status']
    reason = data['reason']

    # Get the current date
    current_date = datetime.now().date()
    # Calculate the date 2 months ago
    two_months_ago = current_date - timedelta(days=60)

    print(f"Updating request for request_id: {request_id}")
    
    try:
        # Fetch the request
        request_obj = WFHRequest.query.get(request_id)
        if not request_obj:
            return jsonify({"message": "Request does not exist"}), 404

        if new_request_status == 'APPROVED':
            staff_id = request_obj.staff_id
            start_date = request_obj.start_date
            end_date = request_obj.end_date

            dates_to_check = []

            if end_date:
                # Recurring request
                current_date_iter = start_date
                while current_date_iter <= end_date:
                    if current_date_iter >= current_date:
                        dates_to_check.append(current_date_iter)
                    current_date_iter += timedelta(days=7)
            else:
                # Single date request
                if start_date >= current_date:
                    dates_to_check.append(start_date)
            violated_dates = []
            # Check WFH policy for each date
            for date_to_check in dates_to_check:
                result = WFHCheckService.check_department_count(staff_id, date_to_check)
                if result != 'Success':
                    violated_dates.append(date_to_check)
            
            if len(violated_dates) > 0:
                formatted_dates = ",".join([d.strftime("%d-%m-%Y") for d in violated_dates])
                return jsonify({"message": f"Cannot approve request due to policy violation on date(s) {formatted_dates}"}), 400

        print("Calling WFHRequestService.update_request()")
        response = WFHRequestService.update_request(
            request_id, new_request_status, two_months_ago, reason)
        if response == True:
            response2 = WFHScheduleService.update_schedule(request_id, new_request_status)

            if response2 == True:
                print("Successfully updated")
                print("===== UPDATE PENDING REQUESTS COMPLETED =====\n")
                return jsonify(f"Successfully updated request {request_id} as {new_request_status}"), 200
                
            else:
                return jsonify({"message": response2}), 404
                
        else:
            return jsonify({"message": response}), 404

    except Exception as e:
        db.session.rollback()
        print("\n===== ERROR OCCURRED =====")
        print(f"An error occurred while processing the WFH request: {str(e)}")
        print("============================\n")
        return jsonify({"message": f"An error occurred: {str(e)}"}), 500


@wfh_bp.route('/reject-expired-request', methods=['POST'])
def reject_expired_request():
    """
    Check the current date against the start date in the database rows.
    If the start date is older than 2 months, update the status to 'REJECTED'.
    """
    try:
        # Get the current date
        current_date = datetime.now().date()

        # Calculate the date 2 months ago
        two_months_ago = current_date - timedelta(days=60)

        # Fetch all requests from the database
        expired = WFHRequestService.reject_expired(two_months_ago)
        return jsonify({"message": f"Updated {expired} requests to 'REJECTED'."}), 200

    except Exception as e:
        db.session.rollback()  # Rollback the session in case of an error
        return jsonify({"message": f"An error occurred: {str(e)}"}), 500
    
@wfh_bp.route('/check-wfh-count', methods=['POST'])
def check_wfh_count():

    data = request.get_json()
    staff_id = data['staff_id']
    date = data['date']
    
    try:
        result = WFHCheckService.check_department_count(staff_id, date)
        return('done')
        print('done')
    except Exception as e:
        db.session.rollback()  # Rollback the session in case of an error
        return jsonify({"message": f"An error occurred: {str(e)}"}), 500


@wfh_bp.route('/manager-schedule-summary/<int:manager_id>', methods=['GET'])
def manager_schedule_summary(manager_id):
    try:
        # Get start_date and end_date from query params, else use default range
        start_date = request.args.get('start_date')
        end_date = request.args.get('end_date')
        today = datetime.now().date()
        if start_date:
            start_date = datetime.strptime(start_date, '%Y-%m-%d').date()
        else:
            start_date = today - timedelta(days=60)  # 2 months before today

        if end_date:
            end_date = datetime.strptime(end_date, '%Y-%m-%d').date()
        else:
            end_date = today + timedelta(days=90)  # 3 months after today

        data = WFHScheduleService.get_manager_schedule_summary(manager_id, start_date, end_date)
        return jsonify(data), 200

    except Exception as e:
        print(f"Error in manager_schedule_summary: {str(e)}")
        return jsonify({"message": f"An error occurred: {str(e)}"}), 500

@wfh_bp.route('/manager-schedule-detail/<int:manager_id>/<date>', methods=['GET'])
def manager_schedule_detail(manager_id, date):
    try:
        date_obj = datetime.strptime(date, '%Y-%m-%d').date()
        data = WFHScheduleService.get_manager_schedule_detail(manager_id, date_obj)
        return jsonify(data), 200
    except Exception as e:
        print(f"Error in manager_schedule_detail: {str(e)}")
        return jsonify({"message": f"An error occurred: {str(e)}"}), 500


File: backend\app\controllers\__init__.py
--------------------------------------------------------------------------------


Directory: backend\app\controllers\__pycache__
--------------------------------------------------------------------------------

File: backend\app\controllers\__pycache__\staff_controller.cpython-312.pyc
--------------------------------------------------------------------------------
Non-code file (content not extracted)


File: backend\app\controllers\__pycache__\wfh_controller.cpython-312.pyc
--------------------------------------------------------------------------------
Non-code file (content not extracted)


File: backend\app\controllers\__pycache__\__init__.cpython-312.pyc
--------------------------------------------------------------------------------
Non-code file (content not extracted)


Directory: backend\app\models
--------------------------------------------------------------------------------

File: backend\app\models\staff.py
--------------------------------------------------------------------------------
from app import db

class Staff(db.Model):
    __tablename__ = 'Staff'

    staff_id = db.Column(db.Integer, primary_key=True)
    staff_fname = db.Column(db.String(255), nullable=False)
    staff_lname = db.Column(db.String(255), nullable=False)
    dept = db.Column(db.String(255), nullable=True)
    position = db.Column(db.String(255), nullable=True)
    country = db.Column(db.String(255), nullable=True)
    email = db.Column(db.String(255), nullable=False, unique=True)
    reporting_manager = db.Column(db.Integer, db.ForeignKey('Staff.staff_id'), nullable=True)
    role = db.Column(db.Integer, nullable=True)
    password = db.Column(db.String(255), nullable=True)

    def to_dict(self):
        return {
            'staff_id': self.staff_id,
            'staff_fname': self.staff_fname,
            'staff_lname': self.staff_lname,
            'dept': self.dept,
            'position': self.position,
            'country': self.country,
            'email': self.email,
            'reporting_manager': self.reporting_manager,
            'role': self.role
        }


File: backend\app\models\wfh_request.py
--------------------------------------------------------------------------------
from app import db
from sqlalchemy.sql import expression

class WFHRequest(db.Model):
    __tablename__ = 'WFHRequest'

    request_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    staff_id = db.Column(db.Integer, db.ForeignKey('Staff.staff_id'), nullable=False)
    manager_id = db.Column(db.Integer, db.ForeignKey('Staff.staff_id'), nullable=False)
    request_date = db.Column(db.Date, nullable=False)
    start_date = db.Column(db.Date, nullable=False)
    end_date = db.Column(db.Date, nullable=True)
    status = db.Column(db.String(20), nullable=False, server_default=expression.text("'PENDING'"))
    reason_for_applying = db.Column(db.Text, nullable=False)
    reason_for_rejection = db.Column(db.Text, nullable=True)

    def to_dict(self):
        end_date = None
        if self.end_date:
            end_date = self.end_date.isoformat()

        return {
            'request_id': self.request_id,
            'staff_id': self.staff_id,
            'manager_id': self.manager_id,
            'request_date': self.request_date.isoformat(),
            'start_date': self.start_date.isoformat(),
            'end_date': end_date,
            'status': self.status,
            'reason_for_applying': self.reason_for_applying,
            'reason_for_rejection': self.reason_for_rejection,
            'is_recurring': self.end_date is not None
        }


File: backend\app\models\wfh_schedule.py
--------------------------------------------------------------------------------
from app import db
from sqlalchemy.sql import expression

class WFHSchedule(db.Model):
    __tablename__ = 'WFHSchedule'

    schedule_id = db.Column(db.Integer, primary_key=True, autoincrement=True)
    request_id = db.Column(db.Integer, db.ForeignKey('WFHRequest.request_id'), nullable=False)
    staff_id = db.Column(db.Integer, db.ForeignKey('Staff.staff_id'), nullable=False)
    manager_id = db.Column(db.Integer, db.ForeignKey('Staff.staff_id'), nullable=False)
    date = db.Column(db.Date, nullable=False)
    duration = db.Column(db.String(20), nullable=False)
    status = db.Column(db.String(20), nullable=False, server_default=expression.text("'PENDING'"))
    dept = db.Column(db.String(50), nullable=False)
    position = db.Column(db.String(50), nullable=False)
    reason_for_withdrawing = db.Column(db.Text, nullable=True)
    

    def to_dict(self):
        return {
            'schedule_id': self.schedule_id,
            'request_id': self.request_id,
            'staff_id': self.staff_id,
            'manager_id': self.manager_id,
            'date': self.date.isoformat(),
            'duration': self.duration,
            'status': self.status,
            'dept': self.dept,
            'position': self.position,
            'reason_for_withdrawing': self.reason_for_withdrawing
        }


File: backend\app\models\__init__.py
--------------------------------------------------------------------------------


Directory: backend\app\models\__pycache__
--------------------------------------------------------------------------------

File: backend\app\models\__pycache__\staff.cpython-312.pyc
--------------------------------------------------------------------------------
Non-code file (content not extracted)


File: backend\app\models\__pycache__\wfh_request.cpython-312.pyc
--------------------------------------------------------------------------------
Non-code file (content not extracted)


File: backend\app\models\__pycache__\wfh_schedule.cpython-312.pyc
--------------------------------------------------------------------------------
Non-code file (content not extracted)


File: backend\app\models\__pycache__\__init__.cpython-312.pyc
--------------------------------------------------------------------------------
Non-code file (content not extracted)


Directory: backend\app\services
--------------------------------------------------------------------------------

File: backend\app\services\staff_service.py
--------------------------------------------------------------------------------
from app.models.staff import Staff
from sqlalchemy.exc import SQLAlchemyError

class StaffService:
    @staticmethod
    def authenticate_staff(email, password):
        try:
            staff = Staff.query.filter_by(email=email).first()
            if staff and staff.password == password:
                return staff
            else:
                print("Wrong email or password")
                return None
            
        except SQLAlchemyError as e:
            print(f"Database error during authentication: {str(e)}")
            raise


    @staticmethod
    def get_staff_by_id(staff_id):
        try:
            staff = Staff.query.get(staff_id)
            if staff is None:
                raise ValueError(f"No staff found with id: {staff_id}")
            return staff
        except SQLAlchemyError as e:
            print(f"Database error while fetching staff by ID: {str(e)}")
            raise
        except ValueError as e:
            print(str(e))
            raise

File: backend\app\services\wfh_check_service.py
--------------------------------------------------------------------------------
from app import db
from app.models.staff import Staff
from app.models.wfh_schedule import WFHSchedule
from app.services.staff_service import StaffService

class WFHCheckService:
    @staticmethod
    def check_department_count(staff_id, date):
        # identify the staff department
        department = StaffService.get_staff_by_id(staff_id).dept

        # find out how many people in that department
        department_count = WFHCheckService.department_count(department)

        # now, find out how many people in that department also have approved WFH on that date
        applied_count = 0

        # get all the schedules with the same date and status 'APPROVED' or 'PENDING'
        schedules = db.session.query(WFHSchedule).filter(
            WFHSchedule.date == date,
            WFHSchedule.status == 'APPROVED')
        
        for schedule in schedules:
            # get the department of the schedule's staff
            schedule_staff = StaffService.get_staff_by_id(schedule.staff_id)
            schedule_department = schedule_staff.dept

            # if department is same as the one applying, increase counter
            if schedule_department == department:
                applied_count += 1

        # Calculate the percentage of staff working from home, if including this request
        wfh_percentage = (applied_count +1) / department_count

        # If more than 50% are working from home, return an error
        if wfh_percentage > 0.5:
            print(f"Max limit for Dept: {department} on date: {date}")
            return 'Unable to apply due to max limit'
        else:
            return 'Success'

    @staticmethod
    def department_count(department):
        staff_count = db.session.query(Staff).filter_by(dept=department).count()
        return staff_count
        
    



File: backend\app\services\wfh_request_service.py
--------------------------------------------------------------------------------
from app import db
from app.models.wfh_request import WFHRequest
from datetime import datetime, timedelta, date

class WFHRequestService:
    @staticmethod
    def create_request(staff_id, manager_id, request_date, start_date, end_date, reason_for_applying):
        error_message = None
        # Check if the start date is valid (within 2 months before or 3 months after today)
        max_valid_date = datetime.now().date() + timedelta(days=90)
        min_valid_date = datetime.now().date() - timedelta(days=60)
        if(not isinstance(start_date, date)):
            start_date = datetime.strptime(start_date, "%Y-%m-%d").date()


        if start_date < min_valid_date or start_date > max_valid_date:
            raise ValueError("Start date must be between 2 months ago and 3 months from now.")
            

        # Check if the end date is valid for recurring requests
        
        if end_date:
            if(not isinstance(end_date, date)):
                print("try2")
                print(type(end_date))
                print(isinstance(end_date, date))
                end_date = datetime.strptime(end_date, "%Y-%m-%d").date()
                print("try2")

            if end_date < min_valid_date or end_date > max_valid_date:
                raise ValueError("End date must be between 2 months ago and 3 months from now.")
            # print(type(end_date))
            # print(type(start_date))
            
            if start_date >= end_date:
                raise ValueError("End date must be after start date.")

        # Check if there's an existing request for the same day
        existing_request = WFHRequest.query.filter(
            WFHRequest.staff_id == staff_id,
            WFHRequest.start_date == start_date,
            WFHRequest.status != 'EXPIRED'
        ).first()

        if existing_request and (not end_date) and existing_request.status != 'REJECTED':
            
            raise ValueError("A request for this date already exists.")

        new_request = WFHRequest(
            staff_id=staff_id,
            manager_id=manager_id,
            request_date=request_date,
            start_date=start_date,
            end_date=end_date,
            reason_for_applying=reason_for_applying
        )
        db.session.add(new_request)
        db.session.commit()
        return new_request
    
    @staticmethod
    def get_pending_requests_for_manager(manager_id):
        return WFHRequest.query.filter_by(manager_id=manager_id, status='PENDING').all()
    

    @staticmethod
    def update_request(request_id, new_request_status, two_months_ago, reason):   #new_request_status = approved / rejected
        # Fetch the request by its ID
        request = WFHRequest.query.get(request_id)
        
        # Check if the request exists
        if request:
            
            # Check if within date range
            request_date = request.start_date
            if WFHRequestService.check_date(request_date , two_months_ago):

                # Update the status field
                request.status = new_request_status

                # provide reason for reject
                if new_request_status == 'REJECTED':
                    request.reason_for_rejection = reason

            # not within date range = not suppose to approve
            else:
                return "The date is invalid to be approved"
            
            # Commit the updated record to the database
            db.session.commit()

            # happy path 
            return True
        
        else:

            # Request does not exist
            return "Request Does not Exist!"
        
    @staticmethod
    def reject_expired(two_months_ago):
        # Fetch all requests from the database
        requests = WFHRequest.query.all()
        updated_count = 0

        # Check each request
        for request in requests:

            # Check if date range is within 2 months and only update those that has not been updated
            if (WFHRequestService.check_date(request.start_date , two_months_ago) != True) and request.status!= 'REJECTED':
                request.status = 'REJECTED'  # Update status to rejected
                request.reason_for_rejection = "Rejected due to past time period" 
                updated_count += 1

        # Commit the changes to the database
        db.session.commit()
        return updated_count
    

    @staticmethod
    def check_date(request_date , two_months_ago):
        if request_date > two_months_ago:
            return True
        else:
            return False

File: backend\app\services\wfh_schedule_service.py
--------------------------------------------------------------------------------
from app import db
from app.models.wfh_schedule import WFHSchedule
from app.models.wfh_request import WFHRequest
from app.models.staff import Staff
from datetime import timedelta

class WFHScheduleService:
    @staticmethod
    def create_schedule(request_id, staff_id, manager_id, start_date, end_date, duration, dept, position):
        schedules = []
        current_date = start_date
        
        while True:

            existing_schedule = WFHSchedule.query.filter(
                WFHSchedule.staff_id == staff_id,
                WFHSchedule.date == current_date,
                WFHSchedule.status != 'EXPIRED'
            ).first()
            
            if existing_schedule and existing_schedule.status != 'REJECTED':
                print(f"Schedule for {current_date} already exists")
                current_date += timedelta(days=7)  # Move to the next week
                if end_date is None or current_date > end_date:
                    break  # If no end_date or we've passed the end_date, stop creating schedule
                continue

            new_schedule = WFHSchedule(
                request_id=request_id,
                staff_id=staff_id,
                manager_id=manager_id,
                date=current_date,
                duration=duration,
                dept=dept,
                position=position
            )
            db.session.add(new_schedule)
            schedules.append(new_schedule)
            print(f"Schedule for {current_date} created successfully")

            current_date += timedelta(days=7)  # Move to the next week
            if end_date is None or current_date > end_date:
                break  # If no end_date or we've passed the end_date, stop creating schedule
        
        if len(schedules) == 0:
            print("No schedules were created. Removing request from entry")
            db.session.delete(WFHRequest.query.get(request_id))
            db.session.commit()
            raise ValueError("No schedules were created")

        
        db.session.commit()
        return schedules
    
    @staticmethod
    def update_schedule(request_id, status):
        # Fetch the existing schedules based on the request_id
        schedules = WFHSchedule.query.filter_by(request_id=request_id).all()

        if not schedules:
            raise ValueError(f"No schedules found for request_id: {request_id}")


        for schedule in schedules:
            if status == "APPROVED":
                schedule.status = "APPROVED"
            elif status == "REJECTED":
                schedule.status = "REJECTED"

        # Commit the updated schedules to the database
        db.session.commit()
        print(f"Schedules for request_id {request_id} have been updated successfully.")

        return True
    
    @staticmethod
    def get_manager_schedule_summary(manager_id, start_date, end_date):
        staff_list = Staff.query.filter_by(reporting_manager=manager_id).all()
        staff_ids = [staff.staff_id for staff in staff_list]
        total_staff = len(staff_ids)
        if total_staff == 0:
            return {'dates': []}

        date_list = []
        current_date = start_date
        while current_date <= end_date:
            date_list.append(current_date)
            current_date += timedelta(days=1)

        dates_data = []
        for d in date_list:
            date_str = d.isoformat()

            # Initialize counts
            wfh_count_am = 0
            wfh_count_pm = 0

            # Get all approved schedules for the date
            schedules = WFHSchedule.query.filter(
                WFHSchedule.staff_id.in_(staff_ids),
                WFHSchedule.date == d,
                WFHSchedule.status == 'APPROVED'
            ).all()

            for sched in schedules:
                if sched.duration == 'FULL_DAY':
                    wfh_count_am += 1
                    wfh_count_pm += 1
                elif sched.duration == 'HALF_DAY_AM':
                    wfh_count_am += 1
                elif sched.duration == 'HALF_DAY_PM':
                    wfh_count_pm += 1

            office_count_am = total_staff - wfh_count_am
            office_count_pm = total_staff - wfh_count_pm

            dates_data.append({
                'date': date_str,
                'total_staff': total_staff,
                'wfh_count_am': wfh_count_am,
                'wfh_count_pm': wfh_count_pm,
                'office_count_am': office_count_am,
                'office_count_pm': office_count_pm
            })
        return {'dates': dates_data}

    @staticmethod
    def get_manager_schedule_detail(manager_id, date):
        staff_list = Staff.query.filter_by(reporting_manager=manager_id).all()
        staff_ids = [staff.staff_id for staff in staff_list]
        if not staff_ids:
            return {'date': date.isoformat(), 'staff': []}

        staff_status = {}
        for staff in staff_list:
            staff_status[staff.staff_id] = {
                'staff_id': staff.staff_id,
                'name': f"{staff.staff_fname} {staff.staff_lname}",
                'position': staff.position,
                'status_am': 'OFFICE',
                'status_pm': 'OFFICE'
            }
        print(staff_status)

        schedules = WFHSchedule.query.filter(
            WFHSchedule.staff_id.in_(staff_ids),
            WFHSchedule.date == date,
            WFHSchedule.status == 'APPROVED'
        ).all()

        for sched in schedules:
            if sched.duration == 'FULL_DAY':
                staff_status[sched.staff_id]['status_am'] = 'WFH'
                staff_status[sched.staff_id]['status_pm'] = 'WFH'
            elif sched.duration == 'HALF_DAY_AM':
                staff_status[sched.staff_id]['status_am'] = 'WFH'
                staff_status[sched.staff_id]['status_pm'] = 'OFFICE'
            elif sched.duration == 'HALF_DAY_PM':
                staff_status[sched.staff_id]['status_am'] = 'OFFICE'
                staff_status[sched.staff_id]['status_pm'] = 'WFH'

        staff_list_status = list(staff_status.values())

        return {
            'date': date.isoformat(),
            'staff': staff_list_status
        }

File: backend\app\services\__init__.py
--------------------------------------------------------------------------------


Directory: backend\app\services\__pycache__
--------------------------------------------------------------------------------

File: backend\app\services\__pycache__\staff_service.cpython-312.pyc
--------------------------------------------------------------------------------
Non-code file (content not extracted)


File: backend\app\services\__pycache__\wfh_check_service.cpython-312.pyc
--------------------------------------------------------------------------------
Non-code file (content not extracted)


File: backend\app\services\__pycache__\wfh_request_service.cpython-312.pyc
--------------------------------------------------------------------------------
Non-code file (content not extracted)


File: backend\app\services\__pycache__\wfh_schedule_service.cpython-312.pyc
--------------------------------------------------------------------------------
Non-code file (content not extracted)


File: backend\app\services\__pycache__\__init__.cpython-312.pyc
--------------------------------------------------------------------------------
Non-code file (content not extracted)


File: backend\app\__init__.py
--------------------------------------------------------------------------------
from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS
from config import Config

# Initialize SQLAlchemy
db = SQLAlchemy()


def create_app(config_class=Config):
    # Initialize Flask app
    app = Flask(__name__)
    app.config.from_object(config_class)

    # Configure CORS
    CORS(app, resources={r"/*": {"origins": "*"}}, supports_credentials=True)

    # Initialize extensions
    db.init_app(app)

    # Import and initialize the staff controller
    from app.controllers import staff_controller, wfh_controller

    app.register_blueprint(staff_controller.staff_bp)
    app.register_blueprint(wfh_controller.wfh_bp)

    @app.route("/")
    def test():
        return "Welcome to the WFH Scheduler API."

    return app


Directory: backend\app\__pycache__
--------------------------------------------------------------------------------

File: backend\app\__pycache__\__init__.cpython-312.pyc
--------------------------------------------------------------------------------
Non-code file (content not extracted)


File: backend\config.py
--------------------------------------------------------------------------------
import os
from dotenv import load_dotenv

# Attempt to load .env file, but don't raise an error if it doesn't exist
load_dotenv(override=True)


class Config:
    # Construct database URI using environment variables
    DB_USER = os.environ.get("DB_USER")
    DB_PASSWORD = os.environ.get("DB_PASSWORD")
    DB_HOST = os.environ.get("DB_HOST")
    DB_NAME = os.environ.get("DB_NAME")

    SQLALCHEMY_DATABASE_URI = (
        f"mysql+mysqlconnector://{DB_USER}:{DB_PASSWORD}@{DB_HOST}/{DB_NAME}"
    )
    SQLALCHEMY_TRACK_MODIFICATIONS = False


class TestConfig(Config):
    TESTING = True
    SQLALCHEMY_DATABASE_URI = "sqlite:///:memory:"
    SQLALCHEMY_TRACK_MODIFICATIONS = False


File: backend\requirements.txt
--------------------------------------------------------------------------------
Flask==3.0.1
Flask-Cors==4.0.0
Flask-SQLAlchemy==3.1.1
python-dotenv==1.0.1
mysql-connector-python==8.3.0
SQLAlchemy==2.0.25
gunicorn==23.0.0

File: backend\run.py
--------------------------------------------------------------------------------
from app import create_app

app = create_app()

if __name__ == '__main__':
    app.run(debug=True)


File: backend\WFH-Schedule.sql
--------------------------------------------------------------------------------
CREATE DATABASE wfh_scheduler;
USE wfh_scheduler;
CREATE TABLE Staff (
    staff_id INT PRIMARY KEY,
    staff_fname VARCHAR(255),
    staff_lname VARCHAR(255),
    dept VARCHAR(255),
    position VARCHAR(255),
    country VARCHAR(255),
    email VARCHAR(255),
    reporting_manager INT,
    role INT,
    password VARCHAR(255),
    FOREIGN KEY (reporting_manager) REFERENCES Staff(staff_id)
);

CREATE TABLE WFHRequest (
    request_id INT PRIMARY KEY AUTO_INCREMENT,
    staff_id INT NOT NULL,
    manager_id INT NOT NULL,
    request_date DATE NOT NULL,
    start_date DATE NOT NULL,
    end_date DATE DEFAULT NULL,
    status VARCHAR(20) DEFAULT 'PENDING', -- 'PENDING', 'REJECTED'
    reason_for_applying TEXT,
    reason_for_rejection TEXT DEFAULT NULL,
    FOREIGN KEY (staff_id) REFERENCES Staff(staff_id),
    FOREIGN KEY (manager_id) REFERENCES Staff(staff_id)
);

CREATE TABLE WFHSchedule (
    schedule_id INT PRIMARY KEY AUTO_INCREMENT,
    request_id INT NOT NULL,
    staff_id INT NOT NULL,
    manager_id INT NOT NULL,
    date DATE NOT NULL,
    duration VARCHAR(20) NOT NULL, -- 'FULL_DAY', 'HALF_DAY_AM', 'HALF_DAY_PM'
    status VARCHAR(20) DEFAULT 'PENDING', -- 'PENDING', 'WITHDRAWN', 'REJECTED'
    dept VARCHAR(255) NOT NULL,
    position VARCHAR(255) NOT NULL,    
    reason_for_withdrawing TEXT DEFAULT NULL,
    FOREIGN KEY (request_id) REFERENCES WFHRequest(request_id),
    FOREIGN KEY (staff_id) REFERENCES Staff(staff_id),
    FOREIGN KEY (manager_id) REFERENCES Staff(staff_id)
);


INSERT INTO Staff (staff_id, staff_fname, staff_lname, dept, position, country, email, reporting_manager, role, password)
VALUES 
(1, 'Test', 'Director', 'Test Department', 'Director', 'Test Country', 'director@test.com', NULL, 1, 'testpassword1'),
(2, 'Test', 'Manager', 'Test Department', 'Manager', 'Test Country', 'manager@test.com', 1, 3, 'testpassword3'),
(3, 'Test', 'Staff', 'Test Department', 'Staff', 'Test Country', 'staff@test.com', 2, 2, 'testpassword2');

-- DROP TABLE `wfh_scheduler`.`WFHSchedule`;
-- DROP TABLE `wfh_scheduler`.`WFHRequest`;



File: frontend\src\views\ScheduleView.vue
--------------------------------------------------------------------------------
<template>
  <div id="app">
    <Navbar />
    <div class="dashboard-container">
      <div class="top-section">
        <h1>Welcome to your Schedule Dashboard</h1>
        <p>You are logged in as {{ user.staff_fname }} {{ user.staff_lname }}</p>
      </div>

      <div class="main-section">
        <div class="calendar-container">
          <FullCalendar 
            :options="calendarOptions"
          />
        </div>
        <div class="right-container" :class="{'show': isRightContainerVisible }" v-html="rightContent">
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, computed } from 'vue';
import Navbar from '@/components/Navbar.vue'
import FullCalendar from '@fullcalendar/vue3';
import dayGridPlugin from '@fullcalendar/daygrid';
import timeGridPlugin from '@fullcalendar/timegrid';
import interactionPlugin from '@fullcalendar/interaction';
import axios from 'axios';

const user = ref({});
const selectedDate = ref('');
const events = ref([]);
const rightContent = ref('');
const isRightContainerVisible = ref(false);

onMounted(() => {
  const storedUser = localStorage.getItem('user');
  if (storedUser) {
    user.value = JSON.parse(storedUser);
    if (user.value.role === 3) {
      fetchManagerScheduleSummary(computeMinDate.value, computeMaxDate.value);
    }
  }
});

const computeMinDate = computed(() => {
  const date = new Date();
  date.setMonth(date.getMonth() - 2);
  date.setDate(date.getDate());
  return date;
});

const computeMaxDate = computed(() => {
  const date = new Date();
  date.setMonth(date.getMonth() + 3);
  return date;
});

function getColorForPercentage(percentage) {
  percentage = parseFloat(percentage);
  if (percentage < 50) {
    return '#FF6B6B'; // Red
  } else if (percentage < 75) {
    return '#FFD93D'; // Yellow
  } else {
    return '#6BCB77'; // Green
  }
}

async function fetchManagerScheduleSummary(start, end) {
  try {
    const startDateStr = start.toISOString().split('T')[0];
    const endDateStr = end.toISOString().split('T')[0];

    const response = await axios.get(`${import.meta.env.VITE_API_URL}/api/manager-schedule-summary/${user.value.staff_id}`, {
      params: {
        start_date: startDateStr,
        end_date: endDateStr
      }
    });

    const data = response.data.dates;
    const newEvents = [];

    data.forEach(item => {
      const dateStr = item.date;
      const totalStaff = item.total_staff;
      const officeCountAm = item.office_count_am;
      const officeCountPm = item.office_count_pm;

      const percentageAm = ((officeCountAm / totalStaff) * 100).toFixed(2);
      const percentagePm = ((officeCountPm / totalStaff) * 100).toFixed(2);

      const colorAm = getColorForPercentage(percentageAm);
      const colorPm = getColorForPercentage(percentagePm);

      newEvents.push({
        title: `AM: ${officeCountAm} / ${totalStaff}`,
        start: dateStr + 'T09:00:00',
        end: dateStr + 'T13:00:00',
        allDay: false,
        extendedProps: {
          timeOfDay: 'AM',
          officeCount: officeCountAm,
          totalStaff,
          percentage: percentageAm
        },
        backgroundColor: colorAm,
        borderColor: colorAm
      });

      newEvents.push({
        title: `PM: ${officeCountPm} / ${totalStaff}`,
        start: dateStr + 'T14:00:00',
        end: dateStr + 'T18:00:00',
        allDay: false,
        extendedProps: {
          timeOfDay: 'PM',
          officeCount: officeCountPm,
          totalStaff,
          percentage: percentagePm
        },
        backgroundColor: colorPm,
        borderColor: colorPm
      });
    });

    events.value = newEvents;
  } catch (error) {
    console.error('Error fetching manager schedule summary:', error);
  }
}

const calendarOptions = computed(() => ({
  plugins: [dayGridPlugin, timeGridPlugin, interactionPlugin],
  initialView: 'dayGridMonth',
  headerToolbar: {
    left: 'prev,next today',
    center: 'title',
    right: 'dayGridMonth,timeGridWeek,timeGridDay',
  },
  dateClick: handleDateClick,
  eventClick: handleEventClick,
  selectable: true,
  validRange: {
    start: computeMinDate.value,
    end: computeMaxDate.value,
  },
  weekends: false,
  events: events.value,
  allDaySlot: false, // Removed the all-day row
  eventContent: function(arg) {
    if (arg.event.extendedProps.timeOfDay) {
      const timeOfDay = arg.event.extendedProps.timeOfDay;
      const officeCount = arg.event.extendedProps.officeCount;
      const totalStaff = arg.event.extendedProps.totalStaff;
      const percentage = arg.event.extendedProps.percentage;

      const container = document.createElement('div');
      container.style.display = 'flex';
      container.style.justifyContent = 'center';
      container.style.alignItems = 'center';
      container.style.backgroundColor = arg.event.backgroundColor;
      container.style.color = '#000000';
      container.style.borderRadius = '4px';
      container.style.margin = '1px 0';
      container.style.padding = '0 2px';
      container.style.fontSize = '0.8em';
      container.style.width = '100%';
      container.style.height = '100%';

      container.textContent = `${timeOfDay}: ${officeCount} / ${totalStaff}`;

      return { domNodes: [container] };
    }
    return null;
  },
  eventClassNames: 'calendar-event',
  slotMinTime: '09:00:00',
  slotMaxTime: '18:00:00',
  businessHours: {
    startTime: '09:00',
    endTime: '18:00',
    daysOfWeek: [1, 2, 3, 4, 5],
  },
}));

function getDateStr(dateObj) {
  return dateObj.toISOString().split('T')[0];
}

async function handleDateClick(info) {
  // Only proceed if we're in month view
  if (info.view.type === 'dayGridMonth') {
    const clickedDate = new Date(info.dateStr);

    if (clickedDate < computeMinDate.value +1) {
      alert('You cannot select a date before two months ago.');
    } else if (clickedDate > computeMaxDate.value) {
      alert('You cannot select a date beyond three months ahead.');
    } else {
      selectedDate.value = info.dateStr;
      if (user.value.role === 3) {
        await displayDateDetails(info.dateStr);
      }
    }
  }
}

async function handleEventClick(info) {
  if (user.value.role === 3) {
    const dateStr = getDateStr(info.event.start);
    const viewType = info.view.type;
    // Only proceed if we're in week or day view
    if (viewType === 'timeGridWeek' || viewType === 'timeGridDay') {
      selectedDate.value = dateStr;
      await displayDateDetails(dateStr);
    }
  }
}

async function displayDateDetails(dateStr) {
  const detailData = await fetchManagerScheduleDetail(dateStr);
  if (detailData) {
    // Group staff data by time slot and team
    let groupedData = {
      AM: {},
      PM: {}
    };

    detailData.staff.forEach(staff => {
      const position = staff.position || 'Unknown';

      // AM
      if (!groupedData.AM[position]) {
        groupedData.AM[position] = { inOffice: 0, wfh: 0, staff: [] };
      }
      if (staff.status_am === 'OFFICE') {
        groupedData.AM[position].inOffice +=1;
      } else {
        groupedData.AM[position].wfh +=1;
      }
      groupedData.AM[position].staff.push({ name: staff.name, status: staff.status_am });

      // PM
      if (!groupedData.PM[position]) {
        groupedData.PM[position] = { inOffice: 0, wfh: 0, staff: [] };
      }
      if (staff.status_pm === 'OFFICE') {
        groupedData.PM[position].inOffice +=1;
      } else {
        groupedData.PM[position].wfh +=1;
      }
      groupedData.PM[position].staff.push({ name: staff.name, status: staff.status_pm });
    });

    let rightContentHtml = `
      <div class="right-container-header">
        <h2>Details for ${dateStr}</h2>
      </div>
    `;

    for (const timeSlot of ['AM', 'PM']) {
      rightContentHtml += `<details open>
        <summary>${timeSlot}</summary>
      `;

      const teams = groupedData[timeSlot];
      for (const [team, data] of Object.entries(teams)) {
        rightContentHtml += `<details open style="margin-left:1em;">
          <summary>${team} - In Office: ${data.inOffice}, WFH: ${data.wfh}</summary>
          <ul>
        `;

        data.staff.forEach(staff => {
          rightContentHtml += `<li>${staff.name}: ${staff.status}</li>`;
        });

        rightContentHtml += `</ul></details>`;
      }

      rightContentHtml += `</details>`;
    }

    rightContent.value = rightContentHtml;
    isRightContainerVisible.value = true;
  } else {
    alert('Error fetching details for the selected date.');
  }
}

async function fetchManagerScheduleDetail(dateStr) {
  try {
    const response = await axios.get(`${import.meta.env.VITE_API_URL}/api/manager-schedule-detail/${user.value.staff_id}/${dateStr}`);
    return response.data;
  } catch (error) {
    console.error('Error fetching manager schedule detail:', error);
    return null;
  }
}

// Make sure to declare all functions used in the template
defineExpose({ fetchManagerScheduleSummary, handleDateClick, handleEventClick });
</script>

<style scoped>
.dashboard-container {
  display: flex;
  flex-direction: column;
  height: 100vh;
}

.top-section {
  padding: 2rem;
  text-align: left;
}

.main-section {
  display: flex;
  height: 100%;
  flex-wrap: wrap;
}

.calendar-container {
  width: 70%; /* Calendar occupies 70% */
  padding: 1rem;
  flex: 1;
  min-width: 300px;
}

.right-container {
  width: 30%; /* Right card occupies 30% */
  padding: 1rem;
  background-color: #f5f5f5;
  border-left: 1px solid #ddd;
  overflow-y: auto;
  max-height: 100vh;
  flex: 1;
  min-width: 300px;
}

.right-container.show {
  display: block;
}

.right-container-header {
  margin-bottom: 1rem;
}

.right-container details {
  margin: 0.5em 0;
}

.right-container summary {
  font-weight: bold;
  cursor: pointer;
}

.right-container ul {
  list-style-type: none;
  padding-left: 1em;
}

.right-container li {
  margin-bottom: 0.5em;
}

.fc {
  width: 100%;
  height: 100%;
}

:deep(.calendar-event) {
  background: transparent!important;
  border: none!important;
}

:deep(.fc-event-main) {
  padding: 2px;
}

:deep(.fc-daygrid-event) {
  white-space: normal!important;
}

:deep(.fc-event-title) {
  color: #000000!important;
}

:deep(.fc-daygrid-event-harness) {
  margin-bottom: 2px!important;
}

:deep(.calendar-event *) {
  color: #000000!important;
}

/* Media query for responsive layout */
@media (max-width: 768px) {
  .main-section {
    flex-direction: column;
  }

  .right-container {
    order: -1; /* Right card appears on top */
    width: 100%;
  }

  .calendar-container {
    width: 100%;
  }
}
</style>