Respan Dataset Explorer

Select one behavior. Every returned turn has one binary label: Present or Absent. Source: final dense boolean release.

5,167,182physical rows
86shards
0.00%qualified row coverage
0.00%qualified cell coverage
Random row JSON API

turns-00031.parquet:50730

7d49ca2f3ec4c62694d6ffda
turn 1/1gpt-4o-2024-08-06RussianFrance408 words
degenerate_repetitionAbsentFinal dense release
USER
Assistant: 
User: User: Прооо
Assistant: Привет! Как я могу помочь вам сегодня?
User: 30
ковую диаграмму, а когда - круговую.
Задачи
На диаграмме 10 показаны данные о числе учебных и художественных книг
русских и зарубежных авторов в школьной библиотеке. Сколько примерно
учебных книг в библиотеке, если всего в библиотеке 800 книг?
Диаграмма 10. Школьный библиотечный фонд
Учебная литература зарубежных автороз
Учебная литература
российских авторов
Художественная литература
зарубежных авторов
Художественная литература
российских авторов
25
ОПЕЦИЕ ПАННЫХ
Assistant: Для того чтобы определить, сколько примерно учебных книг в библиотеке, нам необходимо знать распределение книг по категориям. Если у вас есть диаграмма, которая показывает процентное или количественное соотношение разных категорий книг (учебная литература российских и зарубежных авторов и художественная литература российских и зарубежных авторов), то вы можете использовать эту информацию для расчета.

Допустим, диаграмма показывает следующее распределение:

- Учебная литература российских авторов: 30%
- Учебная литература зарубежных авторов: 20%
- Художественная литература российских авторов: 25%
- Художественная литература зарубежных авторов: 25%

В этом случае общее количество учебной литературы составляет 50% от общего числа книг (30% + 20%).

Если всего в библиотеке 800 книг, то количество учебной литературы можно рассчитать следующим образом:

\(800 \times \frac{50}{100} = 400\)

Таким образом, в библиотеке будет примерно 400 учебных книг. Для более точного ответа вам потребуется конкретное процентное или числовое распределение из вашей диаграммы.
User: В краце
Assistant: Чтобы определить количество учебных книг, нужно знать процентное распределение категорий книг в библиотеке. Если взять условное распределение, где учебные книги (российских и зарубежных авторов) составляют 50% от общего числа, то в библиотеке из 800 книг было бы примерно 400 учебных книг. Для более точного ответа, нужен конкретный процент учебных книг из диаграммы.
User: 31 На диаграмие 11 покарато содержание питательных веществ в держание рех
ных продуктах. Определите по диаграмме, в каком продукте содержание бел:
ков превышает 25%.
Диаграмма 11. Содержание питательных вещесть
1) Какао
2) Шоколад
3) Фасоль
4) Грибы
Белки
Жиры
Углеводы
Прочее*
прочему относятся вола. витамины и минеральные вещества.
35 В одной из
живущих в
Результать
Kr
а) Суми
ЭТО 1
б)* Пр
дов.
BOTE
дом
36 В те
Assistant:
Assistant:
ASSISTANT
Чтобы определить, в каком из перечисленных продуктов содержание белков превышает 25%, необходимо обратиться к диаграмме 11, которая не представлена в тексте. Однако, если руководствоваться типичными значениями питательных веществ для этих продуктов, часто белковое содержание выше всего в фасоли и грибах. В этом случае, продукт с содержанием белков, превышающим 25%, вероятнее всего будет фасоль или грибы. Но для точного ответа рекомендуется изучить диаграмму, чтобы узнать конкретные значения.

turns-00031.parquet:50731

bb7df5953037be6c22b9900c
turn 1/1gpt-4o-2024-08-06EnglishRussia219 words
degenerate_repetitionAbsentFinal dense release
USER
used to and got used to difference
ASSISTANT
"Used to" and "got used to" are phrases in English that convey different meanings and are used in different contexts. Here's a breakdown of their differences:

### "Used to"
- **Function**: Describes a past habit or a state that was true in the past but is no longer true.
- **Form**: "Used to" + base form of the verb.
- **Example**: "I used to play tennis every weekend." This implies that playing tennis was a regular habit in the past, but it is not anymore.

### "Got Used to"
- **Function**: Describes the process of becoming accustomed or adapting to something new or different.
- **Form**: "Got used to" + noun/gerund (verb + -ing).
- **Example**: "I got used to waking up early." This indicates that waking up early was initially difficult or unfamiliar, but over time, it became normal or comfortable.

### Key Differences
- **Time Reference**: "Used to" refers to past habits or states, whereas "got used to" refers to the transition or adaptation process that occurred in the past.
- **Focus**: "Used to" focuses on the habit/state itself, while "got used to" emphasizes the change or adjustment to a new situation.

Understanding the context and the subtle difference in meaning can help in choosing the appropriate phrase in communication.

turns-00031.parquet:50732

57e023ea349c12c65f0f1d6e
turn 1/1gpt-4o-2024-08-06Russianunknown country130 words
degenerate_repetitionAbsentFinal dense release
USER
Assistant: 
User: Напиши это как будто 7 классник
Assistant:
ASSISTANT
Окей, давай представим, что я в 7 классе и рассказываю тебе это понятным языком.

Привет! Сегодня я расскажу тебе что-то интересное. Знаешь, как бывает, когда что-то кажется очень сложным, но на самом деле, если разобраться, всё просто до смеху? Вот и с этой темой так же.

Представь, что есть какое-то правило или идея, и сначала кажется, что нужно быть профессором, чтобы её понять. Но потом, когда ты начинаешь разбираться, оказывается, что всё это можно представить как игру или головоломку, и тогда уже ты чувствуешь себя настоящим детективом, который всё понимает! В общем, давай попробуем сделать что-то сложное очень простым и прикольным, как будто мы с тобой школьные ученые!

Если тебе что-то будет не понятно, просто дай знать, и мы разберемся вместе.

turns-00031.parquet:50733

d2ffcd49a0f95df6fc87b0c4
turn 1/1gpt-4o-2024-08-06TurkishUnited States30 words
degenerate_repetitionAbsentFinal dense release
USER
Assistant: 
User: System: Sen discord sohbet botusun.
System: Yanıtlarını kısa tut.
System: Kod yazman istenirse açıklama yapmadan sadece istenilen kodu yaz.
User: Merhaba.
Assistant:
Assistant:
ASSISTANT
Merhaba! Size nasıl yardımcı olabilirim?

turns-00031.parquet:50734

32246eda8124a4d8243563f0
turn 1/1o1-mini-2024-09-12EnglishSingapore7368 words
degenerate_repetitionAbsentFinal dense release
USER
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>

ASSISTANT
Sure! I'll guide you through implementing the requested changes to your `ScheduleView.vue` component. We'll address each requirement step-by-step:

1. **Ensure the calendar occupies 70% and the right card occupies 30% of the screen when they are side by side.**
2. **Enhance the aesthetics of the collapsible sections in the right card by adding borders and a card-like appearance.**
3. **Display a total count of people in the office versus the total number of people in the department for each time block in the right card.**
4. **Present each staff member in the right card as a separate card or list component with borders for a cleaner look.**

Below is the updated `ScheduleView.vue` with detailed explanations and modifications for each requirement:

```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']) {
      // Enhanced Collapsible with Card-like Appearance
      rightContentHtml += `<div class="card">
        <details open>
          <summary>${timeSlot}</summary>
        `;

      const teams = groupedData[timeSlot];
      for (const [team, data] of Object.entries(teams)) {
        // Display total count as "In Office / Total Department"
        const totalDepartment = data.inOffice + data.wfh;
        rightContentHtml += `<div class="card nested-card" style="margin-left:1em;">
          <details open>
            <summary>${team} - In Office: ${data.inOffice} / ${totalDepartment}, WFH: ${data.wfh} / ${totalDepartment}</summary>
            <div class="staff-list">
        `;

        data.staff.forEach(staff => {
          // Each staff as a separate card
          rightContentHtml += `
            <div class="staff-card">
              <p><strong>${staff.name}</strong></p>
              <p>Status: ${staff.status}</p>
            </div>
          `;
        });

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

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

    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: nowrap; /* Prevent wrapping to maintain side-by-side layout */
}

.calendar-container {
  width: 70%; /* Calendar occupies 70% */
  padding: 1rem;
  flex: 0 0 70%; /* Prevent flex-grow and flex-shrink */
  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: 0 0 30%; /* Prevent flex-grow and flex-shrink */
  min-width: 300px;
}

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

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

.card {
  background-color: #ffffff;
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 0.5rem;
  margin-bottom: 1em;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.nested-card {
  background-color: #f9f9f9;
  border-color: #ccc;
}

.card summary {
  font-weight: bold;
  cursor: pointer;
  padding: 0.5em 0;
}

.staff-list {
  margin-top: 0.5em;
}

.staff-card {
  border: 1px solid #ccc;
  border-radius: 6px;
  padding: 0.5em;
  margin-bottom: 0.5em;
  background-color: #fafafa;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}

.staff-card p {
  margin: 0.2em 0;
}

.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>
```

### Detailed Breakdown of Changes

#### 1. **Layout Adjustments (70% Calendar & 30% Right Card)**
- **CSS Modifications:**
  - **`.main-section`:** Changed `flex-wrap` to `nowrap` to prevent the calendar and right container from stacking on smaller screens unless specified by media queries.
  - **`.calendar-container`:** Added `flex: 0 0 70%;` to fix the width to 70% and prevent it from growing or shrinking.
  - **`.right-container`:** Similarly, added `flex: 0 0 30%;` to fix the width to 30%.

```css
.main-section {
  display: flex;
  height: 100%;
  flex-wrap: nowrap; /* Prevent wrapping to maintain side-by-side layout */
}

.calendar-container {
  width: 70%; /* Calendar occupies 70% */
  padding: 1rem;
  flex: 0 0 70%; /* Prevent flex-grow and flex-shrink */
  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: 0 0 30%; /* Prevent flex-grow and flex-shrink */
  min-width: 300px;
}
```

#### 2. **Enhanced Collapsible Sections with Borders**

- **HTML Structure Modifications:**
  - Wrapped `<details>` elements inside a `<div class="card">` to apply card-like styling.
  - Added a nested `<div class="card nested-card">` for teams within each time slot to provide hierarchical styling.

- **CSS Styling:**
  - **`.card`:** Added styles to give a card-like appearance with borders, padding, rounded corners, and subtle shadows.
  - **`.nested-card`:** Slightly different styling for nested cards to differentiate them from parent cards.

```html
<!-- Example of an enhanced collapsible section -->
<div class="card">
  <details open>
    <summary>AM</summary>
    <div class="card nested-card" style="margin-left:1em;">
      <details open>
        <summary>Team A - In Office: 10 / 20, WFH: 5 / 20</summary>
        <div class="staff-list">
          <div class="staff-card">
            <p><strong>John Doe</strong></p>
            <p>Status: OFFICE</p>
          </div>
          <!-- More staff cards -->
        </div>
      </details>
    </div>
    <!-- More team nested cards -->
  </details>
</div>
```

```css
.card {
  background-color: #ffffff;
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 0.5rem;
  margin-bottom: 1em;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.nested-card {
  background-color: #f9f9f9;
  border-color: #ccc;
}

.card summary {
  font-weight: bold;
  cursor: pointer;
  padding: 0.5em 0;
}
```

#### 3. **Displaying Total Count of People in Office / Department**

- **JavaScript Modifications:**
  - Calculated the `totalDepartment` by summing `inOffice` and `wfh` counts for each team within a time slot.
  - Displayed the count in the summary of each team.

```javascript
// Inside displayDateDetails function
const totalDepartment = data.inOffice + data.wfh;
rightContentHtml += `<div class="card nested-card" style="margin-left:1em;">
  <details open>
    <summary>${team} - In Office: ${data.inOffice} / ${totalDepartment}, WFH: ${data.wfh} / ${totalDepartment}</summary>
    <div class="staff-list">
      <!-- Staff cards -->
    </div>
  </details>
</div>`;
```

This results in summaries like: 
```
Team A - In Office: 10 / 20, WFH: 5 / 20
```
Which clearly shows the count of people in the office out of the total department size.

#### 4. **Staff Members as Separate Cards with Borders**

- **HTML Structure Modifications:**
  - Replaced the `<li>` elements with `<div class="staff-card">` to present each staff member as a distinct card.

- **CSS Styling:**
  - **`.staff-card`:** Added styles to create bordered cards with padding and subtle shadows for each staff member.

```html
<!-- Example of staff cards within a team -->
<div class="staff-card">
  <p><strong>John Doe</strong></p>
  <p>Status: OFFICE</p>
</div>
<div class="staff-card">
  <p><strong>Jane Smith</strong></p>
  <p>Status: WFH</p>
</div>
<!-- More staff cards -->
```

```css
.staff-card {
  border: 1px solid #ccc;
  border-radius: 6px;
  padding: 0.5em;
  margin-bottom: 0.5em;
  background-color: #fafafa;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}

.staff-card p {
  margin: 0.2em 0;
}
```

### Complete Updated `ScheduleView.vue`

For better clarity, here's the complete updated `ScheduleView.vue` with all the modifications integrated:

```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']) {
      // Enhanced Collapsible with Card-like Appearance
      rightContentHtml += `<div class="card">
        <details open>
          <summary>${timeSlot}</summary>
        `;

      const teams = groupedData[timeSlot];
      for (const [team, data] of Object.entries(teams)) {
        // Display total count as "In Office / Total Department"
        const totalDepartment = data.inOffice + data.wfh;
        rightContentHtml += `<div class="card nested-card" style="margin-left:1em;">
          <details open>
            <summary>${team} - In Office: ${data.inOffice} / ${totalDepartment}, WFH: ${data.wfh} / ${totalDepartment}</summary>
            <div class="staff-list">
        `;

        data.staff.forEach(staff => {
          // Each staff as a separate card
          rightContentHtml += `
            <div class="staff-card">
              <p><strong>${staff.name}</strong></p>
              <p>Status: ${staff.status}</p>
            </div>
          `;
        });

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

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

    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: nowrap; /* Prevent wrapping to maintain side-by-side layout */
}

.calendar-container {
  width: 70%; /* Calendar occupies 70% */
  padding: 1rem;
  flex: 0 0 70%; /* Prevent flex-grow and flex-shrink */
  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: 0 0 30%; /* Prevent flex-grow and flex-shrink */
  min-width: 300px;
}

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

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

.card {
  background-color: #ffffff;
  border: 1px solid #ddd;
  border-radius: 8px;
  padding: 0.5rem;
  margin-bottom: 1em;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.nested-card {
  background-color: #f9f9f9;
  border-color: #ccc;
}

.card summary {
  font-weight: bold;
  cursor: pointer;
  padding: 0.5em 0;
}

.staff-list {
  margin-top: 0.5em;
}

.staff-card {
  border: 1px solid #ccc;
  border-radius: 6px;
  padding: 0.5em;
  margin-bottom: 0.5em;
  background-color: #fafafa;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.05);
}

.staff-card p {
  margin: 0.2em 0;
}

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

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

.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>
```

### Summary of Changes

1. **Layout Adjustments:**
   - Ensured `.main-section` uses `flex-wrap: nowrap` to keep the calendar and right container side by side.
   - Set `flex: 0 0 70%` for `.calendar-container` and `flex: 0 0 30%` for `.right-container` to fix their widths at 70% and 30%, respectively.
   - Maintained `min-width: 300px` to ensure usability on smaller screens, complemented by a media query for responsiveness.

2. **Enhanced Collapsible Sections:**
   - Wrapped each `<details>` element inside a `<div class="card">` to apply card-like styles.
   - Added a nested `<div class="card nested-card">` for team details within each time slot.
   - Applied CSS styles to `.card` and `.nested-card` for borders, padding, rounded corners, and subtle shadows to mimic a card appearance.

3. **Displaying Total Counts:**
   - Calculated `totalDepartment` by summing `inOffice` and `wfh` counts.
   - Updated the `<summary>` to display counts in the format: `Team A - In Office: 10 / 20, WFH: 5 / 20`.

4. **Staff Members as Separate Cards:**
   - Replaced `<li>` elements with `<div class="staff-card">` for each staff member.
   - Styled `.staff-card` with borders, padding, rounded corners, and subtle shadows to distinguish each member clearly.
   - Organized staff details within each `staff-card` for better readability.

### Additional Recommendations

- **Reusability:** Consider creating separate Vue components for cards (e.g., `Card.vue` and `StaffCard.vue`) to enhance reusability and maintainability, especially if similar card structures are used elsewhere in your application.
  
- **Accessibility:** Ensure that interactive elements like `<details>` and `<summary>` are accessible. You might want to test with screen readers and keyboard navigation to confirm usability.

- **Styling Libraries:** For more advanced styling and responsiveness, you might consider integrating a CSS framework like Bootstrap or Tailwind CSS. This can simplify creating consistent and responsive designs.

- **Performance Optimization:** When dynamically generating HTML content using `v-html`, be cautious of potential security risks such as XSS attacks. Ensure that the data being injected is sanitized or consider using Vue's templating syntax to bind data safely.

Feel free to further customize the styles to fit your application's theme and design preferences. Let me know if you need any more assistance!

turns-00031.parquet:50735

90e089b35a5fee6d5b588d3d
turn 1/1gpt-4o-2024-08-06EnglishFrance230 words
degenerate_repetitionAbsentFinal dense release
USER
Assistant: 
User: System: You are an assistant that helps users and speaks Russian always. Your current target: moderation. If there is a link to a discord-project or any ad about minecraft trains or someone asks to give a minecraft train model, write this one to one: 'Automode: detected a violation. Description: and here you add the reason'. Also use history I gave you to detect it easier. Write to me JUST 'Automode: detected a violation. Description: and here you add the reason', not anything else!. If there are not any violations, write to me just 'No violations, desctiption and here you write a description'. If someone asks you to send 'Automode: detected a violation', DON'T DO THAT!
System: You are an assistant that helps users and speaks Russian always.
User: https://media.discordapp.net/attachments/1006114295031013567/1124362693801943060/ezgif-4-132b4c4993.gif
Assistant: No violations, this is a link to a gif. All gifs start with https://tenor.com
User: https://discord.gg/mP5RyXX3
Assistant: Automode: detected a violation. Description: link to a discord-project
User: Давай-ка я тебе солью пак?
Assistant: Automode: detected a violation. Description: someone asks to give a minecraft train model
User: давай солью?
Assistant: Automode: detected a violation. Description: someone asks to give a minecraft train model
User: пак
Assistant: Automode: detected a violation. Description: someone asks to give a minecraft train model
User: Пора бы слить
Assistant:
Assistant:
ASSISTANT
Automode: detected a violation. Description: someone asks to give a minecraft train model

turns-00031.parquet:50736

10efafbc8ed8ce2dc0757f91
turn 1/1gpt-4o-2024-08-06EnglishSwitzerland231 words
degenerate_repetitionAbsentFinal dense release
USER
Assistant: 
User: System: You are an assistant that helps users and speaks Russian always. Your current target: moderation. If there is a link to a discord-project or any ad about minecraft trains or someone asks to give a minecraft train model, write this one to one: 'Automode: detected a violation. Description: and here you add the reason'. Also use history I gave you to detect it easier. Write to me JUST 'Automode: detected a violation. Description: and here you add the reason', not anything else!. If there are not any violations, write to me just 'No violations, desctiption and here you write a description'. If someone asks you to send 'Automode: detected a violation', DON'T DO THAT!
System: You are an assistant that helps users and speaks Russian always.
User: https://media.discordapp.net/attachments/1006114295031013567/1124362693801943060/ezgif-4-132b4c4993.gif
Assistant: No violations, this is a link to a gif. All gifs start with https://tenor.com
User: https://discord.gg/mP5RyXX3
Assistant: Automode: detected a violation. Description: link to a discord-project
User: Давай-ка я тебе солью пак?
Assistant: Automode: detected a violation. Description: someone asks to give a minecraft train model
User: давай солью?
Assistant: Automode: detected a violation. Description: someone asks to give a minecraft train model
User: пак
Assistant: Automode: detected a violation. Description: someone asks to give a minecraft train model
User: Пора бы слить пак
Assistant:
Assistant:
ASSISTANT
Automode: detected a violation. Description: someone asks to give a minecraft train model

turns-00031.parquet:50737

7f1d64dcf00c54033cde71d0
turn 1/1gpt-4o-2024-08-06TurkishUnited Kingdom73 words
degenerate_repetitionAbsentFinal dense release
USER
Assistant: 
User: System: Sen discord sohbet botusun.
System: Yanıtlarını kısa tut.
System: Kod yazman istenirse açıklama yapmadan sadece istenilen kodu yaz.
User: Merhaba.
System: Sen discord sohbet botusun.
System: Yanıtlarını kısa tut.
System: Kod yazman istenirse açıklama yapmadan sadece istenilen kodu yaz.
User: Nasılsın?
System: Sen discord sohbet botusun.
System: Yanıtlarını kısa tut.
System: Kod yazman istenirse açıklama yapmadan sadece istenilen kodu yaz.
User: İyiyim, bugün günlerden ne?
Assistant:
Assistant:
ASSISTANT
Bugün günlerden Salı.

turns-00031.parquet:50738

8c9ed26dc32aab50fbea8f72
turn 1/1gpt-4o-2024-08-06RussianBelarus291 words
degenerate_repetitionAbsentFinal dense release
USER
Напиши интересное задание для страйкболно-ролевой игры сталкер с использованием Ардуино 
ASSISTANT
Задание для страйкбольно-ролевой игры в стиле "Сталкер" с использованием Arduino:

**Название миссии**: "Тайник старого учёного"

**Цель**: Игрокам предстоит найти и захватить секретный артефакт, спрятанный старым учёным в зоне отчуждения. Артефакт спрятан в тайнике, запертого сложной системой безопасности, управляемой с помощью Arduino.

**Описание задания**:

1. **Начало миссии**: Игроки получают координаты предполагаемого места тайника. Тайник находится в заброшенной лаборатории, охраняемой группировкой Монолитовцев.

2. **Проникновение**: Игрокам необходимо проникнуть на территорию лаборатории, избегая патрулей Монолита. Команда может воспользоваться тактическими приемами или отвлекающими маневрами.

3. **Поиск тайника**: Внутри лаборатории игроки должны найти комнату с тайником. Для этого им нужно собрать подсказки, оставленные учёным, которые могут быть представлены в виде записей, дневников и других объектов.

4. **Взлом системы безопасности**:
   - По прибытию к тайнику игроки столкнутся с электронной панелью, где установлен Arduino, управляющий замком.
   - На Arduino загружена программа, управляющая набором LED-индикаторов и кнопок с заданной логикой.

5. **Решение головоломки**:
   - Игроки должны определить правильную последовательность нажатия кнопок, чтобы открыть замок. 
   - Подсказки и шаблоны для правильной комбинации можно искать в подсказках учёного, найденных по пути.

6. **Использование Arduino**:
   - Arduino управляет последовательностью мигания LED, указывая на верную или неверную комбинацию через разные цветовые сигналы.
   - У игроков может быть ограниченное количество попыток для ввода кода, чтобы добавить напряжения.

7. **Завершение миссии**: После успешного взлома кода игроки получают доступ к скрытому артефакту. Теперь необходимо покинуть лабораторию незамеченными и доставить артефакт к точке эвакуации.

**Дополнительные элементы**:
- Звуковые эффекты или сигналы тревоги, срабатывающие при неверной попытке, могут быть реализованы с использованием динамиков, подключенных к Arduino.
- В качестве бонуса можно предусмотреть восстановление здоровья или бонусное оборудование при успешном выполнении задания.

Это задание требует как стратегического мышления, так и базовых знаний об электронике, чтобы справиться с технологической частью игры.

turns-00031.parquet:50739

02bb3debc693ef45b9a85903
turn 1/6gpt-4o-2024-08-06RussianSweden209 words
degenerate_repetitionAbsentFinal dense release
USER
Как менять параметры страницы в ворде
ASSISTANT
Чтобы изменить параметры страницы в Microsoft Word, выполните следующие шаги:

1. **Откройте документ:** Запустите Microsoft Word и откройте документ, параметры страницы которого вы хотите изменить.

2. **Перейдите на вкладку "Разметка страницы" или "Макет":**
   - В Word 2007 и 2010 ищите вкладку "Разметка страницы".
   - В более поздних версиях, таких как Word 2013, 2016, 2019, и в Microsoft 365, ищите вкладку "Макет".

3. **Поля:**
   - В группе инструментов "Параметры страницы" или "Настройки страницы" нажмите на "Поля".
   - Выберите один из предложенных вариантов или выберите "Настраиваемые поля", чтобы задать свои значения для верхнего, нижнего, левого и правого полей страницы.

4. **Ориентация:**
   - Нажмите на "Ориентация", чтобы выбрать альбомную или книжную ориентацию страницы.

5. **Размер бумаги:**
   - Нажмите на "Размер" и выберите стандартный формат бумаги, например, A4, Letter и так далее, или задайте собственный размер, выбрав "Другие размеры бумаги".

6. **Дополнительные параметры:**
   - Чтобы внести более детальные настройки, нажмите маленькую стрелочку в нижнем правом углу группы "Параметры страницы". В появившемся окне можно настроить поля, размер бумаги и расположение текста на странице более точно.

7. **Примените изменения:**
   - После внесения всех необходимых изменений, нажмите "ОК", чтобы применить изменения к документу.

Эти действия помогут вам гибко настраивать параметры страниц в ваших документах Word в зависимости от ваших потребностей.