Help me create comprehensive frontend testing for each of my vue file using vitest. MAke it coverage 100%. Use mock to ensure fake data being parsed. 

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,
    duration VARCHAR(20) NOT NULL, -- 'FULL_DAY', 'HALF_DAY_AM', 'HALF_DAY_PM'
    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 
-- CEO with 7 directors
(1, 'Test', 'CEO', 'CEO', 'MD', 'Singapore', 'ceo@test.com', NULL, 1, 'password'),
(2, 'Sales', 'Director', 'Sales', 'Director', 'Singapore', 'salesdirector@test.com', 1, 1, 'password'),
(3, 'Consultancy', 'Director', 'Consultancy', 'Director', 'Singapore', 'consultancydirector@test.com', 1, 1, 'password'),
(4, 'Solutioning', 'Director', 'Solutioning', 'Director', 'Singapore', 'solutioningdirector@test.com', 1, 1, 'password'),
(5, 'Engineering', 'Director', 'Engineering', 'Director', 'Singapore', 'engineeringdirector@test.com', 1, 1, 'password'),
(6, 'HR', 'Director', 'HR', 'Director', 'Singapore', 'hrdirector@test.com', 1, 1, 'password'),
(7, 'Finance', 'Director', 'Finance', 'Director', 'Singapore', 'financedirector@test.com', 1, 1, 'password'),
(8, 'IT', 'Director', 'IT', 'Director', 'Singapore', 'itdirector@test.com', 1, 1, 'password'),


-- Sales with 2 managers and 4 staff
(9, 'Sales', 'Manager1', 'Sales', 'Sales Manager', 'Singapore', 'salesmanager1@test.com', 2, 3, 'password'),
(10, 'Sales', 'Manager2', 'Sales', 'Sales Manager', 'Singapore', 'salesmanager2@test.com', 2, 3, 'password'),
(11, 'Sales', 'Staff1', 'Sales', 'Account Manager', 'Singapore', 'salesstaff1@test.com', 9, 2, 'password'),
(12, 'Sales', 'Staff2', 'Sales', 'Account Manager', 'Singapore', 'salesstaff2@test.com', 9, 2, 'password'),
(13, 'Sales', 'Staff3', 'Sales', 'Account Manager', 'Singapore', 'salesstaff3@test.com', 10, 2, 'password'),
(14, 'Sales', 'Staff4', 'Sales', 'Account Manager', 'Singapore', 'salesstaff4@test.com', 10, 2, 'password'),

-- Consultancy with 2 consultants
(15, 'Consultancy', 'Staff1', 'Consultancy', 'Consultant', 'Singapore', 'consultant1@test.com', 3, 2, 'password'),
(16, 'Consultancy', 'Staff2', 'Consultancy', 'Consultant', 'Singapore', 'consultant2@test.com', 3, 2, 'password'),

-- Solutioning with 2 developers and 2 support team staff
(17, 'Solutioning', 'Developer1', 'Solutioning', 'Developer', 'Singapore', 'developer1@test.com', 4, 2, 'password'),
(18, 'Solutioning', 'Developer2', 'Solutioning', 'Developer', 'Singapore', 'developer2@test.com', 4, 2, 'password'),
(19, 'Solutioning', 'Support1', 'Solutioning', 'Support', 'Singapore', 'support1@test.com', 4, 2, 'password'),
(20, 'Solutioning', 'Support2', 'Solutioning', 'Support', 'Singapore', 'support2@test.com', 4, 2, 'password'),

-- Engineering with 2 senior engineers, 2 junior engineers, 2 call centre, 2 operation planning team
(21, 'Engineering', 'SeniorEngineer1', 'Engineering', 'Senior Engineer', 'Singapore', 'se1@test.com', 5, 2, 'password'),
(22, 'Engineering', 'SeniorEngineer2', 'Engineering', 'Senior Engineer', 'Singapore', 'se2@test.com', 5, 2, 'password'),
(23, 'Engineering', 'JuniorEngineer1', 'Engineering', 'Junior Engineer', 'Singapore', 'je1@test.com', 5, 2, 'password'),
(24, 'Engineering', 'JuniorEngineer2', 'Engineering', 'Junior Engineer', 'Singapore', 'je2@test.com', 5, 2, 'password'),
(25, 'Engineering', 'CallCentre1', 'Engineering', 'Call Centre', 'Singapore', 'cc1@test.com', 5, 2, 'password'),
(26, 'Engineering', 'CallCentre2', 'Engineering', 'Call Centre', 'Singapore', 'cc2@test.com', 5, 2, 'password'),
(27, 'Engineering', 'OperationPlanning1', 'Engineering', 'Operation Planning', 'Singapore', 'op1@test.com', 5, 2, 'password'),
(28, 'Engineering', 'OperationPlanning2', 'Engineering', 'Operation Planning', 'Singapore', 'op2@test.com', 5, 2, 'password'),

-- HR with 2 HR Team, 2 LD Team, 2 Admin Team
(29, 'HR', 'HR1', 'HR', 'HR Team', 'Singapore', 'hr1@test.com', 6, 1, 'password'),
(30, 'HR', 'HR2', 'HR', 'HR Team', 'Singapore', 'hr2@test.com', 6, 1, 'password'),
(31, 'HR', 'LD1', 'HR', 'LD Team', 'Singapore', 'ld1@test.com', 6, 2, 'password'),
(32, 'HR', 'LD2', 'HR', 'LD Team', 'Singapore', 'ld2@test.com', 6, 2, 'password'),
(33, 'HR', 'Admin1', 'HR', 'Admin Team', 'Singapore', 'hradmin1@test.com', 6, 2, 'password'),
(34, 'HR', 'Admin2', 'HR', 'Admin Team', 'Singapore', 'hradmin2@test.com', 6, 2, 'password'),

-- Finance with 2 Finance Manager, 5 Finance Executive
(35, 'Finance', 'Manager1', 'Finance', 'Finance Manager', 'Singapore', 'financemanager1@test.com', 7, 3, 'password'),
(36, 'Finance', 'Manager2', 'Finance', 'Finance Manager', 'Singapore', 'financemanager2@test.com', 7, 3, 'password'),
(37, 'Finance', 'Staff1', 'Finance', 'Finance Executive', 'Singapore', 'financestaff1@test.com', 35, 2, 'password'),
(38, 'Finance', 'Staff2', 'Finance', 'Finance Executive', 'Singapore', 'financestaff2@test.com', 35, 2, 'password'),
(39, 'Finance', 'Staff3', 'Finance', 'Finance Executive', 'Singapore', 'financestaff3@test.com', 36, 2, 'password'),
(40, 'Finance', 'Staff4', 'Finance', 'Finance Executive', 'Singapore', 'financestaff4@test.com', 36, 2, 'password'),
(41, 'Finance', 'Staff5', 'Finance', 'Finance Executive', 'Singapore', 'financestaff5@test.com', 36, 2, 'password'),

-- IT with 2 IT Team, 2 IT Support Team
(42, 'IT', 'IT1', 'IT', 'IT Team', 'Singapore', 'it1@test.com', 8, 2, 'password'),
(43, 'IT', 'IT2', 'IT', 'IT Team', 'Singapore', 'it2@test.com', 8, 2, 'password');


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



Directory: frontend\src\router
--------------------------------------------------------------------------------

File: frontend\src\router\index.js
--------------------------------------------------------------------------------
import { createRouter, createWebHistory } from 'vue-router'
import LoginView from '../views/LoginView.vue'
import ScheduleView from '../views/ScheduleView.vue'
import RequestView from '@/views/RequestView.vue'
import ApplicationView from '@/views/ApplicationView.vue'
import ManagerScheduleView from '@/views/ManagerTeamScheduleView.vue'
import TeamScheduleView from '@/views/StaffTeamScheduleView.vue'
import HRScheduleView from '@/views/HRScheduleView.vue'
import StaffRequestView from '@/views/StaffRequestView.vue'
import DirectorTeamScheduleView from '@/views/DirectorTeamScheduleView.vue'

const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: '/',
      name: 'login',
      component: LoginView
    },
    {
      path: '/schedule',
      name: 'schedule',
      component: ScheduleView,
      meta: { requiresAuth: true }
    },
    {
      path: '/teamschedule-manager',
      name: 'managerschedule',
      component: ManagerScheduleView,
      meta: { requiresAuth: true }
    },
    {
      path: '/teamschedule-staff',
      name: 'teamschedule',
      component: TeamScheduleView,
      meta: { requiresAuth: true }
    },
    {
      path: '/requests',
      name: 'requests',
      component: RequestView,
      meta: { requiresAuth: true }
    },
    {
      path: '/application',
      name: 'application',
      component: ApplicationView,
      meta: { requiresAuth: true }
    },
    {
      path: '/overallschedule',
      name: 'overallschedule',
      component: HRScheduleView,
      meta: { requiresAuth: true }
    },
    {
      path: '/request-list',
      name: 'request-list',
      component: StaffRequestView,
      meta: { requiresAuth: true }
    },
    {
      path: '/teamschedule-director',
      name: 'directorschedule',
      component: DirectorTeamScheduleView,
      meta: {requiresAuth: true}
    },
    // ... other routes ...
  ]
})

router.beforeEach((to, from, next) => {
  if (to.matched.some(record => record.meta.requiresAuth)) {
    if (!localStorage.getItem('user')) {
      next('/')
    } else {
      next()
    }
  } else {
    next()
  }
})

export default router

Directory: frontend\src\tests
--------------------------------------------------------------------------------

File: frontend\src\tests\ApplicationView.spec.js
--------------------------------------------------------------------------------
// tests/ApplicationView.spec.js
import { mount } from '@vue/test-utils';
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import ApplicationView from '@/views/ApplicationView.vue';
import axios from 'axios';
import router from '@/router';
import store from '@/store';

vi.mock('axios');

describe('ApplicationView.vue', () => {
  let wrapper;

  beforeEach(() => {
    wrapper = mount(ApplicationView, {
      global: {
        plugins: [store, router],
        stubs: ['DatePicker'], // If you're using any external components like date pickers
      },
    });
  });

  afterEach(() => {
    vi.resetAllMocks();
  });

  it('Valid WFH Request - Single Full Day', async () => {
    axios.post.mockResolvedValueOnce({ data: { success: true } });

    await wrapper.find('input[name="startDate"]').setValue('2023-10-18'); // Use a valid weekday
    await wrapper.find('select[name="dayType"]').setValue('Full Day');
    await wrapper.find('textarea[name="reason"]').setValue('Personal appointment.');
    await wrapper.find('form').trigger('submit.prevent');

    expect(axios.post).toHaveBeenCalledWith('/api/wfh-requests', {
      startDate: '2023-10-18',
      dayType: 'Full Day',
      recurring: false,
      reason: 'Personal appointment.',
    });

    expect(wrapper.text()).toContain('WFH request submitted successfully.');
  });

  it('Valid WFH Request - Single Half Day AM', async () => {
    axios.post.mockResolvedValueOnce({ data: { success: true } });

    await wrapper.find('input[name="startDate"]').setValue('2023-10-18');
    await wrapper.find('select[name="dayType"]').setValue('Half Day (AM)');
    await wrapper
      .find('textarea[name="reason"]')
      .setValue('Medical appointment in the morning.');
    await wrapper.find('form').trigger('submit.prevent');

    expect(axios.post).toHaveBeenCalled();
    expect(wrapper.text()).toContain('WFH request submitted successfully.');
  });

  it('Valid WFH Request - Recurring Days', async () => {
    axios.post.mockResolvedValueOnce({ data: { success: true } });

    await wrapper.find('input[name="startDate"]').setValue('2023-10-18');
    await wrapper.find('input[name="endDate"]').setValue('2023-10-25');
    await wrapper.find('input[name="recurring"]').setChecked(true);
    await wrapper.find('select[name="dayType"]').setValue('Full Day');
    await wrapper
      .find('textarea[name="reason"]')
      .setValue('Ongoing family care obligations.');
    await wrapper.find('form').trigger('submit.prevent');

    expect(axios.post).toHaveBeenCalledWith('/api/wfh-requests', {
      startDate: '2023-10-18',
      endDate: '2023-10-25',
      dayType: 'Full Day',
      recurring: true,
      reason: 'Ongoing family care obligations.',
    });

    expect(wrapper.text()).toContain('WFH request submitted successfully.');
  });

  it('Invalid WFH Request - Start Date on Weekend', async () => {
    await wrapper.find('input[name="startDate"]').setValue('2023-10-21'); // Assume this is a Saturday
    await wrapper.find('select[name="dayType"]').setValue('Full Day');
    await wrapper.find('textarea[name="reason"]').setValue('Any reason');
    await wrapper.find('form').trigger('submit.prevent');

    expect(wrapper.text()).toContain('Start date cannot be a weekend.');
  });

  it('Invalid WFH Request - Start Date in Past Beyond 2 Months', async () => {
    await wrapper.find('input[name="startDate"]').setValue('2023-07-01'); // More than 2 months ago
    await wrapper.find('select[name="dayType"]').setValue('Full Day');
    await wrapper.find('textarea[name="reason"]').setValue('Any reason');
    await wrapper.find('form').trigger('submit.prevent');

    expect(wrapper.text()).toContain('Start date cannot be more than 2 months ago.');
  });

  it('Invalid WFH Request - End Date Before Start Date', async () => {
    await wrapper.find('input[name="startDate"]').setValue('2023-10-20');
    await wrapper.find('input[name="endDate"]').setValue('2023-10-18');
    await wrapper.find('input[name="recurring"]').setChecked(true);
    await wrapper.find('select[name="dayType"]').setValue('Full Day');
    await wrapper.find('textarea[name="reason"]').setValue('Any reason');
    await wrapper.find('form').trigger('submit.prevent');

    expect(wrapper.text()).toContain('End date must be after the start date.');
  });

  it('Invalid WFH Request - Missing Reason', async () => {
    await wrapper.find('input[name="startDate"]').setValue('2023-10-18');
    await wrapper.find('select[name="dayType"]').setValue('Full Day');
    await wrapper.find('form').trigger('submit.prevent');

    expect(wrapper.text()).toContain('Reason is required.');
  });

  it('Testing Date Boundaries', async () => {
    axios.post.mockResolvedValueOnce({ data: { success: true } });

    const startDate = new Date();
    startDate.setMonth(startDate.getMonth() - 2);
    const endDate = new Date();
    endDate.setMonth(endDate.getMonth() + 3);

    await wrapper
      .find('input[name="startDate"]')
      .setValue(startDate.toISOString().substr(0, 10));
    await wrapper
      .find('input[name="endDate"]')
      .setValue(endDate.toISOString().substr(0, 10));
    await wrapper.find('select[name="dayType"]').setValue('Full Day');
    await wrapper.find('textarea[name="reason"]').setValue('Boundary test.');
    await wrapper.find('form').trigger('submit.prevent');

    expect(wrapper.text()).toContain('WFH request submitted successfully.');
  });

  it('Attempt to Access Without Authentication', async () => {
    // Assuming you have route guards that redirect unauthenticated users
    // Mock the store to show the user is not authenticated
    const authenticatedStore = {
      ...store,
      state: { ...store.state, isAuthenticated: false },
    };

    wrapper = mount(ApplicationView, {
      global: {
        plugins: [authenticatedStore, router],
      },
    });

    await router.push('/application');
    expect(wrapper.vm.$route.path).toBe('/login');
  });

  it('Staff with No Manager Assigned', async () => {
    // Mock user data with no reporting_manager
    store.state.user = { reporting_manager: null };
    await wrapper.find('input[name="startDate"]').setValue('2023-10-18');
    await wrapper.find('select[name="dayType"]').setValue('Full Day');
    await wrapper.find('textarea[name="reason"]').setValue('Test reason');
    await wrapper.find('form').trigger('submit.prevent');

    expect(wrapper.text()).toContain('No manager assigned.');
  });

  it('Cross-Field Validation', async () => {
    await wrapper.find('input[name="startDate"]').setValue('2023-10-18');
    await wrapper.find('input[name="endDate"]').setValue('2023-10-25');
    await wrapper.find('input[name="recurring"]').setChecked(false);
    await wrapper.find('select[name="dayType"]').setValue('Full Day');
    await wrapper.find('textarea[name="reason"]').setValue('Test reason');
    await wrapper.find('form').trigger('submit.prevent');

    // Ensure that endDate is ignored
    expect(axios.post).toHaveBeenCalledWith(
      '/api/wfh-requests',
      expect.objectContaining({
        endDate: undefined, // Or null, depending on your implementation
      })
    );
  });

  it('Boundary Testing for Day Types', async () => {
    await wrapper.find('input[name="startDate"]').setValue('2023-10-18');
    await wrapper.find('textarea[name="reason"]').setValue('Test reason');
    await wrapper.find('form').trigger('submit.prevent');

    expect(wrapper.text()).toContain('Day type is required.');
  });
});


File: frontend\src\tests\DirectorTeamScheduleView.spec.js
--------------------------------------------------------------------------------
// tests/DirectorTeamScheduleView.spec.js
import { mount } from '@vue/test-utils';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import DirectorTeamScheduleView from '@/views/DirectorTeamScheduleView.vue';
import axios from 'axios';
import router from '@/router';
import store from '@/store';

vi.mock('axios');

describe('DirectorTeamScheduleView.vue', () => {
  let wrapper;

  beforeEach(() => {
    // Mock API response for department schedule
    axios.get.mockResolvedValue({
      data: {
        events: [
          /* ... */
        ],
        departmentDetails: [
          /* ... */
        ],
      },
    });

    wrapper = mount(DirectorTeamScheduleView, {
      global: {
        plugins: [store, router],
      },
    });
  });

  it('Director Views Department Schedule', async () => {
    expect(wrapper.exists()).toBe(true);
    await wrapper.vm.$nextTick();

    expect(wrapper.find('.calendar').exists()).toBe(true);
  });

  it('View Details for a Specific Date', async () => {
    await wrapper.find('.calendar-date[data-date="2023-10-18"]').trigger('click');
    await wrapper.vm.$nextTick();

    expect(wrapper.find('.details-container').exists()).toBe(true);
  });

  it('Switch Between "Direct" and "Manager" Views', async () => {
    expect(wrapper.find('.direct-reports').exists()).toBe(true);
    expect(wrapper.find('.managers-teams').exists()).toBe(true);
  });

  it('Expand Manager Teams and Sub-Teams', async () => {
    const managerCard = wrapper.find('.manager-card');
    await managerCard.trigger('click');
    await wrapper.vm.$nextTick();

    expect(managerCard.classes()).toContain('expanded');

    const subTeamCard = managerCard.find('.sub-team-card');
    await subTeamCard.trigger('click');
    await wrapper.vm.$nextTick();

    expect(subTeamCard.classes()).toContain('expanded');
  });

  it('Verify Team Aggregations', async () => {
    const teamData = wrapper.vm.getTeamData('Manager1');

    expect(teamData.totalInOffice).toBe(10); // Replace with expected values
    expect(teamData.totalWFH).toBe(5);
  });

  it('Access Control', async () => {
    store.state.user = { role: 'Staff' };

    wrapper = mount(DirectorTeamScheduleView, {
      global: {
        plugins: [store, router],
      },
    });

    await wrapper.vm.$nextTick();

    expect(wrapper.vm.$route.path).toBe('/unauthorized');
  });

  it('Test Responsiveness on Small Screens', async () => {
    global.innerWidth = 500; // Simulate small screen
    global.dispatchEvent(new Event('resize'));
    await wrapper.vm.$nextTick();

    expect(wrapper.find('.details-overlay').isVisible()).toBe(true);
  });

  it('Data Integrity After Filtering', async () => {
    // Assuming there's a filter method
    wrapper.vm.applyFilter('Some Criteria');
    await wrapper.vm.$nextTick();

    // Verify data corresponds to the filter
    expect(wrapper.vm.filteredData).toEqual(
      expect.arrayContaining([
        /* Expected filtered data */
      ])
    );
  });

  it('Error Handling on Data Fetch Failures', async () => {
    axios.get.mockRejectedValueOnce(new Error('Network Error'));

    wrapper = mount(DirectorTeamScheduleView, {
      global: {
        plugins: [store, router],
      },
    });

    await wrapper.vm.$nextTick();

    expect(wrapper.text()).toContain('Failed to load data.');
  });
});


File: frontend\src\tests\HRScheduleView.spec.js
--------------------------------------------------------------------------------
// tests/HRScheduleView.spec.js
import { mount } from '@vue/test-utils';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import HRScheduleView from '@/views/HRScheduleView.vue';
import axios from 'axios';
import router from '@/router';
import store from '@/store';

vi.mock('axios');

describe('HRScheduleView.vue', () => {
  let wrapper;

  beforeEach(() => {
    // Mock API response for company schedule
    axios.get.mockResolvedValue({
      data: {
        departments: ['Sales', 'Engineering', 'HR'],
        events: [
          /* ... */
        ],
      },
    });

    wrapper = mount(HRScheduleView, {
      global: {
        plugins: [store, router],
      },
    });
  });

  it('HR Views Overall Company Schedule', async () => {
    expect(wrapper.exists()).toBe(true);
    await wrapper.vm.$nextTick();

    expect(wrapper.find('.calendar').exists()).toBe(true);
  });

  it('Filter Schedules by Department', async () => {
    await wrapper.find('select[name="department"]').setValue('Sales');
    await wrapper.vm.$nextTick();

    expect(wrapper.vm.selectedDepartment).toBe('Sales');
    // Verify that data corresponds to the selected department
  });

  it('View Detailed Department Schedules', async () => {
    await wrapper.find('select[name="department"]').setValue('Sales');
    await wrapper.find('.calendar-date[data-date="2023-10-18"]').trigger('click');
    await wrapper.vm.$nextTick();

    expect(wrapper.find('.details-container').exists()).toBe(true);
    expect(wrapper.find('.department-name').text()).toBe('Sales');
  });

  it('Invalid Department Selection', async () => {
    await wrapper.find('select[name="department"]').setValue('NonExistentDept');
    await wrapper.vm.$nextTick();

    expect(wrapper.text()).toContain('No data available');
  });

  it('Test Department Dropdown Functionality', async () => {
    const departments = ['Sales', 'Engineering', 'HR'];

    for (const dept of departments) {
      await wrapper.find('select[name="department"]').setValue(dept);
      await wrapper.vm.$nextTick();
      expect(wrapper.vm.selectedDepartment).toBe(dept);
      // Optionally, verify data updates
    }
  });

  it('Unauthorized Access', async () => {
    store.state.user = { role: 'Staff' };

    wrapper = mount(HRScheduleView, {
      global: {
        plugins: [store, router],
      },
    });

    await wrapper.vm.$nextTick();

    expect(wrapper.vm.$route.path).toBe('/unauthorized');
  });

  it('Edge Case - No WFH Requests on a Date', async () => {
    // Assuming the date '2023-10-18' has no WFH requests
    await wrapper.find('select[name="department"]').setValue('Sales');
    await wrapper.find('.calendar-date[data-date="2023-10-18"]').trigger('click');
    await wrapper.vm.$nextTick();

    expect(wrapper.text()).toContain('All staff are in office');
  });

  it('Concurrency Test', async () => {
    // Simulate multiple HR users by creating multiple instances
    const wrapper2 = mount(HRScheduleView, {
      global: {
        plugins: [store, router],
      },
    });

    await Promise.all([wrapper.vm.$nextTick(), wrapper2.vm.$nextTick()]);

    expect(wrapper.exists()).toBe(true);
    expect(wrapper2.exists()).toBe(true);
  });

  it('Accessibility Check', async () => {
    // Simulate keyboard navigation
    await wrapper.find('select[name="department"]').trigger('focus');
    await wrapper.find('select[name="department"]').trigger('keydown', { key: 'ArrowDown' });
    await wrapper.vm.$nextTick();

    // Verify that the department changes
    expect(wrapper.vm.selectedDepartment).not.toBeNull();
  });

  it('Data Security', async () => {
    // Ensure data access is restricted
    store.state.user = { role: 'HR', permissions: [] };
    wrapper = mount(HRScheduleView, {
      global: {
        plugins: [store, router],
      },
    });

    await wrapper.vm.$nextTick();

    // Try accessing restricted data
    expect(wrapper.text()).not.toContain('Sensitive Data');
  });
});


File: frontend\src\tests\LoginView.spec.js
--------------------------------------------------------------------------------
// src/tests/LoginView.spec.js
import { mount } from '@vue/test-utils';
import LoginView from '../views/LoginView.vue';
import axios from 'axios';
import { vi } from 'vitest';

vi.mock('axios');

describe('LoginView.vue', () => {
  afterEach(() => {
    vi.clearAllMocks();
  });

  it('renders the login form', () => {
    const wrapper = mount(LoginView);
    expect(wrapper.find('input[type="email"]').exists()).toBe(true);
    expect(wrapper.find('input[type="password"]').exists()).toBe(true);
    expect(wrapper.find('button[type="submit"]').exists()).toBe(true);
  });

  it('logs in successfully with correct credentials', async () => {
    const mockUserData = {
      staff_id: 1,
      staff_fname: 'John',
      staff_lname: 'Doe',
      // ... other user data ...
    };

    axios.post.mockResolvedValue({ data: mockUserData });

    const wrapper = mount(LoginView, {
      global: {
        stubs: ['router-link'],
      },
    });

    await wrapper.find('input#email').setValue('john.doe@example.com');
    await wrapper.find('input#password').setValue('correctpassword');
    await wrapper.find('form').trigger('submit.prevent');

    expect(axios.post).toHaveBeenCalledWith(
      `${import.meta.env.VITE_API_URL}/api/login`,
      {
        email: 'john.doe@example.com',
        password: 'correctpassword',
      }
    );

    // Check that the user was redirected
    // Assuming you have a mock router or check localStorage
    expect(localStorage.getItem('user')).toBe(JSON.stringify(mockUserData));
  });

  it('shows an error message with incorrect credentials', async () => {
    axios.post.mockRejectedValue({
      response: { data: { message: 'Invalid email or password' } },
    });

    const wrapper = mount(LoginView);

    await wrapper.find('input#email').setValue('john.doe@example.com');
    await wrapper.find('input#password').setValue('wrongpassword');
    await wrapper.find('form').trigger('submit.prevent');

    await wrapper.vm.$nextTick();

    expect(wrapper.find('.error-message').text()).toBe('Invalid email or password');
  });
});


File: frontend\src\tests\ManagerTeamScheduleView.spec.js
--------------------------------------------------------------------------------
// tests/ManagerTeamScheduleView.spec.js
import { mount } from '@vue/test-utils';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import ManagerTeamScheduleView from '@/views/ManagerTeamScheduleView.vue';
import axios from 'axios';
import router from '@/router';
import store from '@/store';

vi.mock('axios');

describe('ManagerTeamScheduleView.vue', () => {
  let wrapper;

  beforeEach(() => {
    // Mock API response for team schedule
    axios.get.mockResolvedValue({
      data: {
        // mock calendar events and team data
        events: [
          /* ... */
        ],
        teamDetails: [
          /* ... */
        ],
      },
    });

    wrapper = mount(ManagerTeamScheduleView, {
      global: {
        plugins: [store, router],
      },
    });
  });

  it('Manager Views Team Schedule', async () => {
    expect(wrapper.exists()).toBe(true);
    await wrapper.vm.$nextTick(); // Wait for data fetching

    // Check if calendar is rendered
    expect(wrapper.find('.calendar').exists()).toBe(true);
    // Additional checks as per your implementation
  });

  it('View Details for a Specific Date', async () => {
    // Simulate clicking on a date
    await wrapper.find('.calendar-date[data-date="2023-10-18"]').trigger('click');
    await wrapper.vm.$nextTick();

    // Check if the right container shows details
    expect(wrapper.find('.details-container').exists()).toBe(true);
    // Check tabs
    expect(wrapper.find('.tab.am').exists()).toBe(true);
    expect(wrapper.find('.tab.pm').exists()).toBe(true);
  });

  it('Toggle Between AM and PM Tabs', async () => {
    // Assume default is AM
    expect(wrapper.find('.tab-content.am').isVisible()).toBe(true);

    // Click on PM tab
    await wrapper.find('.tab.pm').trigger('click');
    await wrapper.vm.$nextTick();

    expect(wrapper.find('.tab-content.pm').isVisible()).toBe(true);
    expect(wrapper.find('.tab-content.am').isVisible()).toBe(false);
  });

  it('Expand and Collapse Teams', async () => {
    const teamCard = wrapper.find('.team-card');
    await teamCard.trigger('click');
    await wrapper.vm.$nextTick();

    expect(teamCard.classes()).toContain('expanded');

    await teamCard.trigger('click');
    await wrapper.vm.$nextTick();

    expect(teamCard.classes()).not.toContain('expanded');
  });

  it('Verify Staff Status Indicators', async () => {
    const staffMember = wrapper.find('.staff-member[data-id="1"]');
    const statusIndicator = staffMember.find('.status-indicator');

    expect(statusIndicator.classes()).toContain('status-in-office'); // Or 'status-wfh' depending on the mock data
  });

  it('Edge Case - No Team Members', async () => {
    // Mock API response with no team members
    axios.get.mockResolvedValueOnce({
      data: {
        events: [],
        teamDetails: [],
      },
    });

    wrapper = mount(ManagerTeamScheduleView, {
      global: {
        plugins: [store, router],
      },
    });

    await wrapper.vm.$nextTick();

    expect(wrapper.text()).toContain('No team data available.');
  });

  it('Unauthorized Access Attempt', async () => {
    // Mock user role as 'Staff'
    store.state.user = { role: 'Staff' };

    wrapper = mount(ManagerTeamScheduleView, {
      global: {
        plugins: [store, router],
      },
    });

    await wrapper.vm.$nextTick();

    expect(wrapper.vm.$route.path).toBe('/unauthorized'); // Replace with your unauthorized route
  });

  it('Data Consistency Check', async () => {
    // Assuming you have a method to get data for a specific date
    const dateData = wrapper.vm.getDateData('2023-10-18');

    // Verify that the data matches the mock data
    expect(dateData.inOffice).toBe(5); // Replace with expected values
    expect(dateData.wfh).toBe(2);
  });

  it('Testing Date Range Constraints', async () => {
    const outOfRangeDate = '2024-01-01';

    await wrapper.find(`.calendar-date[data-date="${outOfRangeDate}"]`).trigger('click');
    await wrapper.vm.$nextTick();

    expect(wrapper.text()).toContain('Date out of range.');
  });
});


File: frontend\src\tests\RequestView.spec.js
--------------------------------------------------------------------------------
// tests/RequestView.spec.js
import { mount } from '@vue/test-utils';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import RequestView from '@/views/RequestView.vue';
import axios from 'axios';
import router from '@/router';
import store from '@/store';

vi.mock('axios');

describe('RequestView.vue', () => {
  let wrapper;

  beforeEach(() => {
    // Mock API response for pending requests
    axios.get.mockResolvedValue({
      data: {
        requests: [
          /* ... */
        ],
      },
    });

    wrapper = mount(RequestView, {
      global: {
        plugins: [store, router],
      },
    });
  });

  it('Manager Views Pending Requests', async () => {
    expect(wrapper.exists()).toBe(true);
    await wrapper.vm.$nextTick();

    expect(wrapper.find('.request-list').exists()).toBe(true);
  });

  it('Approve a WFH Request', async () => {
    axios.post.mockResolvedValueOnce({ data: { success: true } });

    await wrapper.find('.approve-button').trigger('click');
    await wrapper.vm.$nextTick();

    expect(axios.post).toHaveBeenCalledWith('/api/wfh-requests/approve', {
      requestId: expect.anything(),
    });

    expect(wrapper.text()).toContain('Request approved.');
  });

  it('Reject a WFH Request with Reason', async () => {
    axios.post.mockResolvedValueOnce({ data: { success: true } });

    await wrapper.find('.reject-button').trigger('click');
    await wrapper.find('textarea[name="rejectionReason"]').setValue('Not feasible.');
    await wrapper.find('.confirm-reject-button').trigger('click');
    await wrapper.vm.$nextTick();

    expect(axios.post).toHaveBeenCalledWith('/api/wfh-requests/reject', {
      requestId: expect.anything(),
      reason: 'Not feasible.',
    });

    expect(wrapper.text()).toContain('Request rejected.');
  });

  it('Invalid Approval Attempt', async () => {
    axios.post.mockRejectedValueOnce(new Error('Request already processed.'));

    await wrapper.find('.approve-button').trigger('click');
    await wrapper.vm.$nextTick();

    expect(wrapper.text()).toContain('Request already processed.');
  });

  it('Bulk Approvals', async () => {
    axios.post.mockResolvedValueOnce({ data: { success: true } });
    // Select multiple requests
    await wrapper.find('.select-all-checkbox').setChecked(true);
    await wrapper.find('.bulk-approve-button').trigger('click');
    await wrapper.vm.$nextTick();

    expect(axios.post).toHaveBeenCalledWith('/api/wfh-requests/bulk-approve', {
      requestIds: expect.any(Array),
    });

    expect(wrapper.text()).toContain('Requests approved.');
  });

  it('Unauthorized Access', async () => {
    // Mock user role as 'Staff'
    store.state.user = { role: 'Staff' };

    wrapper = mount(RequestView, {
      global: {
        plugins: [store, router],
      },
    });

    await wrapper.vm.$nextTick();

    expect(wrapper.vm.$route.path).toBe('/unauthorized');
  });
});


File: frontend\src\tests\ScheduleView.spec.js
--------------------------------------------------------------------------------
// src/tests/ScheduleView.spec.js
import { mount } from '@vue/test-utils';
import ScheduleView from '../views/ScheduleView.vue';
import axios from 'axios';
import { vi } from 'vitest';

vi.mock('axios');

describe('ScheduleView.vue', () => {
  afterEach(() => {
    vi.clearAllMocks();
  });

  it('fetches events on mount', async () => {
    const mockEvents = {
      dates: [
        {
          date: '2023-10-01',
          schedule: 'FullDay',
        },
        // ... other events ...
      ],
    };

    axios.get.mockResolvedValue({ data: mockEvents });

    const wrapper = mount(ScheduleView, {
      global: {
        stubs: ['Navbar', 'FullCalendar'],
      },
    });

    await wrapper.vm.$nextTick();

    expect(axios.get).toHaveBeenCalled();
    expect(wrapper.vm.events).toHaveLength(mockEvents.dates.length);
  });
});


File: frontend\src\tests\StaffRequestView.spec.js
--------------------------------------------------------------------------------
// tests/StaffRequestView.spec.js
import { mount } from '@vue/test-utils';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import StaffRequestView from '@/views/StaffRequestView.vue';
import axios from 'axios';
import router from '@/router';
import store from '@/store';

vi.mock('axios');

describe('StaffRequestView.vue', () => {
  let wrapper;

  beforeEach(() => {
    // Mock API response for user's WFH requests
    axios.get.mockResolvedValue({
      data: {
        requests: [
          /* ... */
        ],
      },
    });

    wrapper = mount(StaffRequestView, {
      global: {
        plugins: [store, router],
      },
    });
  });

  it('View All WFH Requests', async () => {
    expect(wrapper.exists()).toBe(true);
    await wrapper.vm.$nextTick();

    expect(wrapper.find('.request-list').exists()).toBe(true);
  });

  it('Withdraw Pending Request', async () => {
    axios.post.mockResolvedValueOnce({ data: { success: true } });

    await wrapper.find('.withdraw-button').trigger('click');
    await wrapper.vm.$nextTick();

    expect(axios.post).toHaveBeenCalledWith('/api/wfh-requests/withdraw', {
      requestId: expect.anything(),
    });

    expect(wrapper.text()).toContain('Request withdrawn successfully.');
  });

  it('View Details of a Rejected Request', async () => {
    // Assuming the request item shows the 'View Details' button
    await wrapper.find('.view-details-button').trigger('click');
    await wrapper.vm.$nextTick();

    expect(wrapper.find('.rejection-reason').text()).toContain('Reason for rejection');
  });

  it('Submit a New WFH Request', async () => {
    await wrapper.find('.new-request-button').trigger('click');
    await wrapper.vm.$nextTick();

    expect(wrapper.vm.$route.path).toBe('/application');
  });

  it('Attempt to Modify Approved Request', async () => {
    await wrapper.find('.modify-button').trigger('click');
    await wrapper.vm.$nextTick();

    expect(wrapper.text()).toContain('Approved requests cannot be modified.');
  });
});


Directory: frontend\src\views
--------------------------------------------------------------------------------

File: frontend\src\views\ApplicationView copy.vue
--------------------------------------------------------------------------------
<template>
  <div class="login-container">
    <div class="form-container">
      <div class="form-field">
        <label for="date">Date:
          <input type="text" v-model="selectedDate" readonly placeholder="Select a date" class="input-field" />
          <span class="calendar-icon" @click="toggleCalendar">&#128197;</span> <!-- Calendar icon -->
        </label>
      </div>
      <div class="form-field"> <!-- recurring option -->
        <label>
          <input type="radio" value="single" v-model="recurringOption" /> Single Day <!-- default -->
        </label>
        <label>
          <input type="radio" value="recurring" v-model="recurringOption" /> Recurring
        </label>
      </div>
      <div class="form-field" v-if="recurringOption === 'recurring'"> <!-- recurring option -->
        <label for="dayOfWeek">Day of the Week:
          <select class="input-field" v-model="selectedDayOfWeek">
            <option value="mon">Monday</option>
            <option value="tue">Tuesday</option>
            <option value="wed">Wednesday</option>
            <option value="thu">Thursday</option>
            <option value="fri">Friday</option>
            <option value="sat">Saturday</option>
            <option value="sun">Sunday</option>
          </select>
        </label>
      </div>
      <div class="form-field">
        <label for="reasons">Reasons:
          <input type="text" v-model="reasons" placeholder="Enter reasons" class="input-field" />
        </label>
      </div>
      <button @click="applyDate" class="apply-button">Apply</button>
    </div>

    <!-- Calendar popup when icon is clicked -->
    <div v-if="showCalendar" class="calendar-popup">
      <FullCalendar :options="calendarOptions" @dateClick="handleDateClick" />
    </div>
    <div v-if="showCalendar" class="calendar-popup">
      <button class="close-button" @click="closeCalendar">X</button>
      <FullCalendar :options="calendarOptions" />
    </div>
  </div>
</template>

  
  
    
<script>
import { defineComponent, ref } from 'vue';
import FullCalendar from '@fullcalendar/vue3';
import dayGridPlugin from '@fullcalendar/daygrid';
import interactionPlugin from '@fullcalendar/interaction';

export default defineComponent({
  components: {
    FullCalendar,
  },
  setup() {
    const selectedDate = ref(null);

    const isRecurring = ref(false);
    const selectedDayOfWeek = ref(null); 
    const recurringOption = ref("single");

    const reasons = ref("");
    const showCalendar = ref(false);

    const today = new Date();
    const twoMonthsAgo = new Date();
    const threeMonthsAhead = new Date();

    twoMonthsAgo.setMonth(today.getMonth() - 2);
    threeMonthsAhead.setMonth(today.getMonth() + 3);

    const calendarOptions = {
      plugins: [dayGridPlugin, interactionPlugin],
      initialView: 'dayGridMonth',
      selectable: true,
      validRange: {
        start: twoMonthsAgo.toISOString().split('T')[0],  // Format as YYYY-MM-DD
        end: threeMonthsAhead.toISOString().split('T')[0]
      },
      dateClick: handleDateClick,
    };

    function handleDateClick(info) {
      const clickedDate = new Date(info.dateStr);
      if (clickedDate >= twoMonthsAgo && clickedDate <= threeMonthsAhead) {
        selectedDate.value = selectedDate.value === info.dateStr ? null : info.dateStr;
        closeCalendar();
        showModal.value = true;  // Show the modal after clicking a valid date
  
      } else {
        alert("Date is out of the allowed range!");
      }
    }


    function toggleCalendar() {
      showCalendar.value = !showCalendar.value; // Toggle calendar visibility
    }

    function closeCalendar() {
      showCalendar.value = false; // Close the calendar popup
    }

    function getRecurringDates(dayOfWeek) {
      // compute recurring dates for selectedDayOfWeek for next 3 mths

      const recurringDates = [];
      let currentDate = new Date();

      // e.g. if today is WED (currentDate.getDay()=3)
      // and user selects MON (dayOfWeek=1),
      // the immediate recurring date will be 5 days from today
      let dayOffset = (dayOfWeek + 7 - currentDate.getDay()) % 7;
        // %7 keeps the offset within 0-6 days

      // set currentDate to the first recurrence of selectedDayOfWeek
      currentDate.setDate(currentDate.getDate() + dayOffset);

      while (currentDate <= threeMonthsAhead) {
        recurringDates.push(currentDate.toISOString().split('T')[0]); // Format YYYY-MM-DD
        currentDate.setDate(currentDate.getDate() + 7); // Move to the next week
      }

      return recurringDates;
    }

    function applyDate() {
      // validations
      if (!reasons.value) { alert('Provide a reason.'); return; }

      // single-day arrangement
      if (recurringOption.value === 'single' && selectedDate.value) {
        fetchDates([selectedDate.value], false);
      }
      // recurring arrangement
      else if (recurringOption.value === 'recurring' && selectedDate.value) {
        const daysOfWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];

        // takes the index day that matches the selected day in form
        const recurringDates = getRecurringDates(daysOfWeek.indexOf(selectedDayOfWeek.value));
        fetchDates(recurringDates, true);
      }

      function fetchDates(dates, isRecurring) {
        fetch(`${import.meta.env.VITE_API_URL}/api/save-date/${userData.staff_id}`, { // to integrate with backend over here 
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            dates: dates, // local var
            recurring: isRecurring, // local var
            reasons: reasons.value,
          }),
        })
          .then(response => response.json())
          .then(data => {
            // const dateObj = new Date(selectedDate.value);
            // const daysOfWeek = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
            // const dayName = daysOfWeek[dateObj.getDay()];

            if (isRecurring) {
              alert(`${selectedDayOfWeek.value} applied recurring successfully`);
            } else {
              alert(`${dates[0]} applied successfully`);
            }
            closeModal(); // Close the modal after applying the date
          })
          .catch(error => {
            console.error('Error:', error);
          });
      }
    }

      

    return {
      selectedDate,
      isRecurring,
      reasons,
      showCalendar,
      calendarOptions,
      toggleCalendar,
      closeCalendar,
      handleDateClick,
      applyDate,
      selectedDayOfWeek,
      recurringOption
    };
  },
});
</script>

    
  <style>
  .login-container {
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    background-image: linear-gradient(rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.7)), url('@/assets/images/login-image.jpg');
    background-size: cover;
    background-position: center;
  }
  
  .form-container {
    background-color: white; /* Ensures that the form has a visible background */
    padding: 20px;
    border-radius: 10px;
    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
    width: 400px; /* Set a fixed width for better alignment */
  }
  
  .form-field {
    margin-bottom: 15px; /* Space between each field */
    display: flex;
    flex-direction: column; /* Arrange label and input vertically */
  }
  
  .input-field {
    padding: 8px;
    border: 1px solid #ccc;
    border-radius: 4px;
  }
  
  .calendar-icon {
    cursor: pointer;
    margin-top: 5px; /* Space above the icon */
    font-size: 20px; /* Adjust size as needed */
  }
  
  .apply-button {
    padding: 10px;
    background-color: #007bff;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
  }
  
  .apply-button:hover {
    background-color: #0056b3; /* Darker blue on hover */
  }
  
  /* Calendar popup styles */
  .calendar-popup {
  position: absolute; 
  top: 50%; 
  left: 50%; 
  transform: translate(-50%, -50%); 
  width: 500px; 
  height: 450px; /* Set fixed height */
  overflow: hidden; 
  background-color: white; 
  border: 1px solid #ccc; 
  border-radius: 8px; 
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2); 
  z-index: 1000; 
}

.fc {
  height: 100%; 
}

.fc-daygrid {
  height: 100%; 
}

.fc-daygrid-day {
  height: 50px; /* Set fixed height for each day */
  min-height: 50px; /* Ensure a minimum height */
}


  </style>
  

File: frontend\src\views\ApplicationView.vue
--------------------------------------------------------------------------------
<template>
  <div>
    <Navbar />
    <div class="background-container">
      <div class="container py-5">
        <div class="row justify-content-center">
          <div class="col-md-8">
            <div class="card custom-card">
              <div class="card-header">
                <h2 class="mb-0">Work From Home Request Form</h2>
              </div>
              <div class="card-body">
                <form @submit.prevent="submitForm">
                  <!-- Start Date Field -->
                  <div class="mb-4">
                    <label for="startDate" class="form-label">Start Date</label>
                    <div class="input-group">
                      <span class="input-group-text"><i class="fas fa-calendar-alt"></i></span>
                      <input
                        type="date"
                        class="form-control"
                        id="startDate"
                        v-model="startDate"
                        :min="minStartDate"
                        :max="maxDate"
                        required
                        @change="validateDates"
                      >
                    </div>
                    <div v-if="startDateError" class="text-danger mt-1">
                      {{ startDateError }}
                    </div>
                  </div>

                  <!-- Day Type Selection -->
                  <div class="mb-4">
                    <label class="form-label">Select Day Type</label>
                    <div>
                      <div class="form-check form-check-inline">
                        <input
                          class="form-check-input"
                          type="radio"
                          id="fullDay"
                          value="FULL_DAY"
                          v-model="dayType"
                          required
                        >
                        <label class="form-check-label" for="fullDay">Full Day</label>
                      </div>
                      <div class="form-check form-check-inline">
                        <input
                          class="form-check-input"
                          type="radio"
                          id="halfDayAm"
                          value="HALF_DAY_AM"
                          v-model="dayType"
                          required
                        >
                        <label class="form-check-label" for="halfDayAm">Half Day (AM)</label>
                      </div>
                      <div class="form-check form-check-inline">
                        <input
                          class="form-check-input"
                          type="radio"
                          id="halfDayPm"
                          value="HALF_DAY_PM"
                          v-model="dayType"
                          required
                        >
                        <label class="form-check-label" for="halfDayPm">Half Day (PM)</label>
                      </div>
                    </div>
                  </div>

                  <!-- Recurring Request Checkbox -->
                  <div class="mb-4 form-check">
                    <input
                      type="checkbox"
                      class="form-check-input"
                      id="recurring"
                      v-model="isRecurring"
                      @change="handleRecurringChange"
                      :disabled="isRecurringDisabled"
                    >
                    <label class="form-check-label" for="recurring">Recurring Request</label>
                    <div v-if="showRecurringWarning" class="text-danger mt-1">
                      Please select a valid start date before making this a recurring request.
                    </div>
                  </div>

                  <!-- End Date Field (Conditional) -->
                  <div v-if="isRecurring" class="mb-4">
                    <label for="endDate" class="form-label">End Date</label>
                    <div class="input-group">
                      <span class="input-group-text"><i class="fas fa-calendar-alt"></i></span>
                      <input
                        type="date"
                        class="form-control"
                        id="endDate"
                        v-model="endDate"
                        :min="startDate"
                        :max="maxDate"
                        required
                        @change="validateDates"
                      >
                    </div>
                    <div v-if="endDateError" class="text-danger mt-1">
                      {{ endDateError }}
                    </div>
                  </div>

                  <!-- Reason Field -->
                  <div class="mb-4">
                    <label for="reason" class="form-label">Reason</label>
                    <textarea
                      class="form-control"
                      id="reason"
                      v-model="reason"
                      rows="3"
                      required
                    ></textarea>
                  </div>

                  <!-- Submit Button -->
                  <button
                    type="submit"
                    class="btn btn-primary btn-lg w-40"
                    :disabled="!isFormValid"
                  >
                    Submit Request
                  </button>
                </form>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>

</template>

<script>
import axios from 'axios';
import Navbar from '@/components/Navbar.vue'
export default {
  components: {
    Navbar
  },
  data() {
    return {
      startDate: '',
      endDate: '',
      isRecurring: false,
      showRecurringWarning: false,
      reason: '',
      startDateError: '',
      endDateError: '',
      dayType: ''
    }
  },
  computed: {
    minStartDate() {
      const date = new Date();
      date.setMonth(date.getMonth() - 2);
      return this.formatDate(date);
    },
    maxDate() {
      const date = new Date();
      date.setMonth(date.getMonth() + 3);
      return this.formatDate(date);
    },
    isRecurringDisabled() {
      return !this.startDate;
    },
    isFormValid() {
      return (
        this.startDate &&
        (!this.isRecurring || this.endDate) &&
        this.reason &&
        this.dayType &&
        !this.startDateError &&
        !this.endDateError
      );
    }
  },
  methods: {
    formatDate(date) {
      return date.toISOString().split('T')[0];
    },
    handleRecurringChange() {
      if (!this.startDate || this.startDateError) {
        this.showRecurringWarning = true;
        this.isRecurring = false;
      } else {
        this.showRecurringWarning = false;
        if (!this.isRecurring) {
          this.endDate = '';
        }
      }
      this.validateDates();
    },
    validateDates() {
      this.startDateError = '';
      this.endDateError = '';

      const startDate = new Date(this.startDate);
      const endDate = new Date(this.endDate);
      const minDate = new Date(this.minStartDate);
      const maxDate = new Date(this.maxDate);
      const twoMonthsAgo = new Date();
      twoMonthsAgo.setMonth(twoMonthsAgo.getMonth() - 2);
      const threeMonthsAhead = new Date();
      threeMonthsAhead.setMonth(threeMonthsAhead.getMonth() + 3);

      // Check if the start date is a weekend
      if (startDate.getDay() === 0 || startDate.getDay() === 6) {
        this.startDateError = 'Start date cannot be a weekend.';
      } else if (startDate < twoMonthsAgo) {
        this.startDateError = 'Start date cannot be more than 2 months ago.';
      } else if (startDate > threeMonthsAhead) {
        this.startDateError = 'Start date cannot be more than 3 months ahead.';
      } else if (startDate < minDate || startDate > maxDate) {
        this.startDateError = 'Start date must be within the allowed range.';
      }

      if (this.isRecurring) {
        if (endDate <= startDate) {
          this.endDateError = 'End date must be after the start date.';
        } else if (endDate < twoMonthsAgo) {
          this.endDateError = 'End date cannot be more than 2 months ago.';
        } else if (endDate > threeMonthsAhead) {
          this.endDateError = 'End date cannot be more than 3 months ahead.';
        } else if (endDate < minDate || endDate > maxDate) {
          this.endDateError = 'End date must be within the allowed range.';
        }
      }
    },
    async submitForm() {
    if (this.isFormValid) {
      // Prepare the data to send
      const userData = JSON.parse(localStorage.getItem('user'));
      const requestData = {
        staff_id: userData.staff_id,
        manager_id: userData.reporting_manager,
        reason_for_applying: this.reason,
        date: this.startDate,
        duration: this.dayType,
        dept: userData.dept,
        position: userData.position,
        end_date: this.isRecurring ? this.endDate : null,
      };

      try {
        // Make the POST request
        const response = await axios.post(`${import.meta.env.VITE_API_URL}/api/request`, requestData);

        // Success message
        console.log('Request successful:', response.data);
        alert('WFH request submitted successfully.');
        // Reset the form after submission
        this.resetForm();

      } catch (error) {
        // Handle errors (e.g., show error messages)
        console.error('Error submitting WFH request:', error.response.data);
        alert(`Error: ${error.response.data.message}`);
      }
    } else {
      // Shouldn't reach here since the button is disabled when form is invalid
      alert('Please fill out the form correctly.');
    }
  },
  resetForm() {
    this.startDate = '';
    this.endDate = '';
    this.isRecurring = false;
    this.reason = '';
    this.dayType = '';
    this.startDateError = '';
    this.endDateError = '';
    this.showRecurringWarning = false;
  },
  }
}
</script>

<style scoped>
.background-container {
  background-image: linear-gradient(rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.7)), url('@/assets/images/login-image.jpg');
  background-size: cover;
  background-position: center;
  min-height: calc(100vh - 56px);
  display: flex;
  align-items: center;
}

.custom-card {
  box-shadow: 0 10px 20px rgba(0, 0, 0, 0.2);
  border: none;
  border-radius: 15px;
  overflow: hidden;
}

.card-header {
  background-color: #141b4d;
  color: white;
  padding: 1.5rem;
  border-bottom: none;
}

.card-header h2 {
  font-size: 1.8rem;
  font-weight: 600;
  margin-bottom: 0;
}

.card-body {
  padding: 2rem;
  background-color: #f8f9fa;
}

.form-label {
  font-weight: 600;
  color: #141b4d;
  margin-bottom: 0.5rem;
}

.form-control, .input-group-text {
  border-color: #d1d5db;
}

.form-control:focus {
  border-color: #141b4d;
  box-shadow: 0 0 0 0.2rem rgba(20, 27, 77, 0.25);
}

.input-group-text {
  background-color: #e9ecef;
  color: #141b4d;
}

.form-check-input:checked {
  background-color: #141b4d;
  border-color: #141b4d;
}

.form-check-label {
  margin-right: 1rem;
  color: #141b4d;
}

.btn-primary {
  background-color: #141b4d;
  border-color: #141b4d;
  font-weight: 600;
  padding: 0.75rem 1.5rem;
  transition: all 0.3s ease;
  display: block;
  margin: 0 auto;
}

.btn-primary:hover, .btn-primary:focus {
  background-color: #1e2a6d;
  border-color: #1e2a6d;
  transform: translateY(-2px);
}

@media (max-width: 768px) {
  .card-header h2 {
    font-size: 1.5rem;
  }

  .card-body {
    padding: 1.5rem;
  }
}
</style>


File: frontend\src\views\DirectorTeamScheduleView.vue
--------------------------------------------------------------------------------
<template>
    <div id="app">
      <Navbar />
      <div class="dashboard-container">
        <div class="main-section">
          <div class="calendar-container">
            <FullCalendar 
              :options="calendarOptions"
            />
            <div v-if="isLoading" class="loading-overlay">
              <div class="spinner"></div>
            </div>
          </div>
          <div class="right-container" :class="{'show': isRightContainerVisible }">
            <div v-if="selectedDateDetails" class="details-container">
              <div class="details-header">
                <h2>Details for {{ selectedDate }}</h2>
                <!-- Close button is only rendered for small screens -->
                <button 
                  v-if="isSmallScreen" 
                  class="close-button" 
                  @click="isRightContainerVisible = false"
                >
                  ×
                </button>
              </div>
              <div class="tabs">
                <button 
                  :class="['tab', activeTab === 'AM' ? 'active' : '']" 
                  @click="activeTab = 'AM'"
                >
                  AM
                </button>
                <button 
                  :class="['tab', activeTab === 'PM' ? 'active' : '']" 
                  @click="activeTab = 'PM'"
                >
                  PM
                </button>
              </div>
              <div class="tab-content">
                <div v-if="activeTab === 'AM'">
                  <div class="summary">
                    <div class="summary-item">
                      <span class="summary-label">In Office:</span>
                      <span class="summary-value">{{ selectedDateDetails.AM.inOffice }} / {{ selectedDateDetails.AM.total }}</span>
                    </div>
                    <div class="summary-item">
                      <span class="summary-label">WFH:</span>
                      <span class="summary-value">{{ selectedDateDetails.AM.wfh }} / {{ selectedDateDetails.AM.total }}</span>
                    </div>
                  </div>
                  <div v-if="selectedDateDetails.type === 'direct'">
                    <!-- Existing teams display -->
                    <div class="teams">
                      <div 
                        class="team card" 
                        v-for="team in selectedDateDetails.AM.teams" 
                        :key="team.teamName"
                        @click="toggleTeam('AM', team.teamName)"
                      >
                        <div class="team-header">
                          <h3>{{ team.teamName }}</h3>
                          <div class="badge-container">
                            <span class="badge badge-office">
                              In Office: {{ team.inOffice }}/{{ team.total }}
                            </span>
                            <span class="badge badge-wfh">
                              WFH: {{ team.wfh }}/{{ team.total }}
                            </span>
                          </div>
                        </div>
                        <div class="staff-list" v-if="expandedTeams['AM-' + team.teamName]">
                          <div class="staff-card" v-for="staff in team.staff" :key="staff.staff_id">
                            <span :class="['status-indicator', staff.status === 'OFFICE' ? 'green' : 'yellow']"></span>
                            <span class="staff-name">{{ staff.name }}</span>
                          </div>
                        </div>
                      </div>
                    </div>
                  </div>
                  <div v-else-if="selectedDateDetails.type === 'manager'">
                    <div class="teams">
                      <div 
                        class="team card" 
                        v-for="(managerTeam, managerName) in selectedDateDetails.AM.teams" 
                        :key="managerName"
                        @click="toggleTeam('AM', managerName)"
                      >
                        <div class="team-header">
                          <h3>{{ managerName }}'s Team</h3>
                          <div class="badge-container">
                            <span class="badge badge-office">
                              In Office: {{ managerTeam.inOffice }}/{{ managerTeam.total }}
                            </span>
                            <span class="badge badge-wfh">
                              WFH: {{ managerTeam.wfh }}/{{ managerTeam.total }}
                            </span>
                          </div>
                        </div>
                        <div class="sub-teams" v-if="expandedTeams['AM-' + managerName]">
                          <div 
                            class="sub-team card" 
                            v-for="team in managerTeam.subTeams" 
                            :key="team.teamName"
                            @click.stop="toggleSubTeam('AM', managerName, team.teamName)"
                          >
                            <div class="team-header">
                              <h4>{{ team.teamName }}</h4>
                              <div class="badge-container">
                                <span class="badge badge-office">
                                  In Office: {{ team.inOffice }}/{{ team.total }}
                                </span>
                                <span class="badge badge-wfh">
                                  WFH: {{ team.wfh }}/{{ team.total }}
                                </span>
                              </div>
                            </div>
                            <div class="staff-list" v-if="expandedTeams['AM-' + managerName + '-' + team.teamName]">
                              <div class="staff-card" v-for="staff in team.staff" :key="staff.staff_id">
                                <span :class="['status-indicator', staff.status === 'OFFICE' ? 'green' : 'yellow']"></span>
                                <span class="staff-name">{{ staff.name }}</span>
                              </div>
                            </div>
                          </div>
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
                <!-- Similar adjustments for PM -->
                <div v-else-if="activeTab === 'PM'">
                  <div class="summary">
                    <div class="summary-item">
                      <span class="summary-label">In Office:</span>
                      <span class="summary-value">{{ selectedDateDetails.PM.inOffice }} / {{ selectedDateDetails.PM.total }}</span>
                    </div>
                    <div class="summary-item">
                      <span class="summary-label">WFH:</span>
                      <span class="summary-value">{{ selectedDateDetails.PM.wfh }} / {{ selectedDateDetails.PM.total }}</span>
                    </div>
                  </div>
                  <div v-if="selectedDateDetails.type === 'direct'">
                    <!-- Existing teams display -->
                    <div class="teams">
                      <div 
                        class="team card" 
                        v-for="team in selectedDateDetails.PM.teams" 
                        :key="team.teamName"
                        @click="toggleTeam('PM', team.teamName)"
                      >
                        <div class="team-header">
                          <h3>{{ team.teamName }}</h3>
                          <div class="badge-container">
                            <span class="badge badge-office">
                              In Office: {{ team.inOffice }}/{{ team.total }}
                            </span>
                            <span class="badge badge-wfh">
                              WFH: {{ team.wfh }}/{{ team.total }}
                            </span>
                          </div>
                        </div>
                        <div class="staff-list" v-if="expandedTeams['PM-' + team.teamName]">
                          <div class="staff-card" v-for="staff in team.staff" :key="staff.staff_id">
                            <span :class="['status-indicator', staff.status === 'OFFICE' ? 'green' : 'yellow']"></span>
                            <span class="staff-name">{{ staff.name }}</span>
                          </div>
                        </div>
                      </div>
                    </div>
                  </div>
                  <div v-else-if="selectedDateDetails.type === 'manager'">
                    <div class="teams">
                      <div 
                        class="team card" 
                        v-for="(managerTeam, managerName) in selectedDateDetails.PM.teams" 
                        :key="managerName"
                        @click="toggleTeam('PM', managerName)"
                      >
                        <div class="team-header">
                          <h3>{{ managerName }}'s Team</h3>
                          <div class="badge-container">
                            <span class="badge badge-office">
                              In Office: {{ managerTeam.inOffice }}/{{ managerTeam.total }}
                            </span>
                            <span class="badge badge-wfh">
                              WFH: {{ managerTeam.wfh }}/{{ managerTeam.total }}
                            </span>
                          </div>
                        </div>
                        <div class="sub-teams" v-if="expandedTeams['PM-' + managerName]">
                          <div 
                            class="sub-team card" 
                            v-for="team in managerTeam.subTeams" 
                            :key="team.teamName"
                            @click.stop="toggleSubTeam('PM', managerName, team.teamName)"
                          >
                            <div class="team-header">
                              <h4>{{ team.teamName }}</h4>
                              <div class="badge-container">
                                <span class="badge badge-office">
                                  In Office: {{ team.inOffice }}/{{ team.total }}
                                </span>
                                <span class="badge badge-wfh">
                                  WFH: {{ team.wfh }}/{{ team.total }}
                                </span>
                              </div>
                            </div>
                            <div class="staff-list" v-if="expandedTeams['PM-' + managerName + '-' + team.teamName]">
                              <div class="staff-card" v-for="staff in team.staff" :key="staff.staff_id">
                                <span :class="['status-indicator', staff.status === 'OFFICE' ? 'green' : 'yellow']"></span>
                                <span class="staff-name">{{ staff.name }}</span>
                              </div>
                            </div>
                          </div>
                        </div>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </div>
            <div v-else class="no-selection">
              <p>Select a date on the calendar to view details.</p>
            </div>
          </div>
        </div>
      </div>
    </div>
  </template>
  
  <script setup>
  import { ref, onMounted, computed, onUnmounted } 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';
  
  // Reactive references
  const user = ref({});
  const selectedDate = ref('');
  const events = ref([]);
  const selectedDateDetails = ref(null);
  const isRightContainerVisible = ref(false);
  const isSmallScreen = ref(window.innerWidth < 768); // Reactive property for screen size
  const isLoading = ref(false); // Loading indicator for summary loading
  const activeTab = ref('AM'); // Active tab state
  const expandedTeams = ref({}); // Tracks expanded state of team cards
  
  // Handle window resize to update isSmallScreen
  const handleResize = () => {
    isSmallScreen.value = window.innerWidth < 768;
  };
  
  onMounted(() => {
    const storedUser = localStorage.getItem('user');
    if (storedUser) {
      user.value = JSON.parse(storedUser);
      if (user.value.role === 1) {
        initiateChunkedSummaryLoading();
      }
    }
    window.addEventListener('resize', handleResize);
  });
  
  onUnmounted(() => {
    window.removeEventListener('resize', handleResize);
  });
  
  // Compute minimum and maximum selectable dates
  const computeMinDate = computed(() => {
    const date = new Date();
    date.setMonth(date.getMonth() - 2);
    return date;
  });
  
  const computeMaxDate = computed(() => {
    const date = new Date();
    date.setMonth(date.getMonth() + 3);
    return date;
  });
  
  // Determine color based on percentage
  function getColorForPercentage(percentage) {
    percentage = parseFloat(percentage);
    if (percentage < 50) {
      return '#FF6B6B'; // Red
    } else if (percentage < 75) {
      return '#FFD93D'; // Yellow
    } else {
      return '#6BCB77'; // Green
    }
  }
  
  // Fetch summary data for a given date range
  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 wfhCountAm = item.wfh_count_am;
        const wfhCountPm = item.wfh_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) Office: ${officeCountAm}/${totalStaff}, WFH: ${wfhCountAm}/${totalStaff}`,
          start: dateStr + 'T09:00:00',
          end: dateStr + 'T13:00:00',
          allDay: false,
          extendedProps: {
            timeOfDay: 'AM',
            officeCount: officeCountAm,
            wfhCount: wfhCountAm,
            totalStaff,
            percentage: percentageAm
          },
          backgroundColor: colorAm,
          borderColor: colorAm
        });
  
        newEvents.push({
          title: `(PM) Office: ${officeCountPm}/${totalStaff}, WFH: ${wfhCountPm}/${totalStaff}`,
          start: dateStr + 'T14:00:00',
          end: dateStr + 'T18:00:00',
          allDay: false,
          extendedProps: {
            timeOfDay: 'PM',
            officeCount: officeCountPm,
            wfhCount: wfhCountPm,
            totalStaff,
            percentage: percentagePm
          },
          backgroundColor: colorPm,
          borderColor: colorPm
        });
      });
  
      events.value = [...events.value, ...newEvents];
    } catch (error) {
      console.error('Error fetching manager schedule summary:', error);
    }
  }
  
  // Format date to YYYY-MM-DD
  function getDateStr(dateObj) {
    const year = dateObj.getFullYear();
    const month = ('0' + (dateObj.getMonth() + 1)).slice(-2);
    const day = ('0' + dateObj.getDate()).slice(-2);
    return `${year}-${month}-${day}`;
  }
  
  // Calendar options
  const calendarOptions = computed(() => {
    let smallScreen = isSmallScreen.value;
  
    return {
      plugins: [dayGridPlugin, timeGridPlugin, interactionPlugin],
      initialView: 'dayGridMonth',
      headerToolbar: {
        left: 'prev,next today',
        center: 'title',
        right: 'dayGridMonth,timeGridWeek,timeGridDay',
      },
      dateClick: handleDateClick,
      eventClick: handleEventClick,
      datesSet: handleDatesSet,
      selectable: true,
      validRange: {
        start: computeMinDate.value,
        end: computeMaxDate.value,
      },
      weekends: false,
      events: events.value,
      allDaySlot: false,
      eventContent: function(arg) {
        if (arg.event.extendedProps.timeOfDay) {
          const timeOfDay = arg.event.extendedProps.timeOfDay;
          const officeCount = arg.event.extendedProps.officeCount;
          const wfhCount = arg.event.extendedProps.wfhCount;
          const totalStaff = arg.event.extendedProps.totalStaff;
          const percentage = arg.event.extendedProps.percentage;
  
          const container = document.createElement('div');
          container.style.display = 'flex';
          container.style.flexDirection = 'column';
          container.style.justifyContent = 'center';
          container.style.alignItems = 'flex-start'; // Align badges beside text
          container.style.backgroundColor = arg.event.backgroundColor;
  
          // Assign a class based on background color
          if (arg.event.backgroundColor === '#FFD93D') { // Yellow
            container.classList.add('yellow-event');
          } else {
            container.classList.add('default-event');
          }
  
          container.style.borderRadius = '4px';
          container.style.margin = '1px 0';
          container.style.padding = '2px 4px';
          container.style.fontSize = '0.8em';
          container.style.width = '100%';
          container.style.height = '100%';
  
          // Create a wrapper for the label and badges
          const labelContainer = document.createElement('div');
          labelContainer.style.display = 'flex';
          labelContainer.style.alignItems = 'center';
          labelContainer.style.width = '100%';
          labelContainer.style.justifyContent = 'space-between';
  
          const label = document.createElement('span');
          label.textContent = `${timeOfDay}`;
  
          const badgesWrapper = document.createElement('div');
          badgesWrapper.style.display = 'flex';
          badgesWrapper.style.gap = '4px';
  
          // Create Office Badge
          const officeBadge = document.createElement('span');
          officeBadge.classList.add('badge', 'badge-office-calendar');
          officeBadge.textContent = `Office: ${officeCount}`;
  
          // Create WFH Badge
          const wfhBadge = document.createElement('span');
          wfhBadge.classList.add('badge', 'badge-wfh-calendar');
          wfhBadge.textContent = `WFH: ${wfhCount}`;
  
          badgesWrapper.appendChild(officeBadge);
          badgesWrapper.appendChild(wfhBadge);
  
          labelContainer.appendChild(label);
          labelContainer.appendChild(badgesWrapper);
  
          container.appendChild(labelContainer);
  
          return { domNodes: [container] };
        }
        return null;
      },
      eventDidMount: function(info) {
        // Adjust text color based on background
        const bgColor = info.event.backgroundColor.toUpperCase();
        const yellowHex = '#FFD93D';
  
        if (bgColor === yellowHex) {
          // Set text color to black
          info.el.style.color = '#000000';
  
          // Ensure child elements inherit the color
          const elements = info.el.querySelectorAll('*');
          elements.forEach(el => {
            el.style.color = 'inherit';
          });
        } else {
          // Default text color
          info.el.style.color = '#ffffff';
  
          const elements = info.el.querySelectorAll('*');
          elements.forEach(el => {
            el.style.color = 'inherit';
          });
        }
      },
      eventClassNames: 'calendar-event',
      slotMinTime: '09:00:00',
      slotMaxTime: '19:00:00',
      businessHours: {
        startTime: '09:00',
        endTime: '18:00',
        daysOfWeek: [1, 2, 3, 4, 5],
      },
    };
  });
  
  // Handle date click on the calendar
  async function handleDateClick(info) {
    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;
        activeTab.value = 'AM'; // Reset to AM tab on new selection
        if (user.value.role === 1) {
          await displayDateDetails(info.dateStr);
        }
      }
    }
  }
  
  // Handle event click on the calendar
  async function handleEventClick(info) {
    if (user.value.role === 1) {
      const dateStr = getDateStr(info.event.start);
      const viewType = info.view.type;
      if (viewType === 'timeGridWeek' || viewType === 'timeGridDay') {
        selectedDate.value = dateStr;
        activeTab.value = 'AM'; // Reset to AM tab on new selection
        await displayDateDetails(dateStr);
      }
    }
  }
  
  // Handle when dates are set in the calendar view
  async function handleDatesSet(info) {
    if (info.view.type === 'timeGridDay') {
      const dateStr = getDateStr(info.start);
      selectedDate.value = dateStr;
      activeTab.value = 'AM'; // Reset to AM tab on dates set
      if (user.value.role === 1) {
        const detailData = await fetchManagerScheduleDetail(dateStr);
        if (detailData) {
          await displayDateDetails(dateStr);
        } else {
          alert('Error fetching details for the selected date.');
        }
      }
    }
  }
  
  // Display date details when a date is selected
  async function displayDateDetails(dateStr) {
    const detailData = await fetchManagerScheduleDetail(dateStr);
    if (detailData) {
      let groupedData = {
        AM: {
          inOffice: 0,
          wfh: 0,
          total: 0,
          teams: {}
        },
        PM: {
          inOffice: 0,
          wfh: 0,
          total: 0,
          teams: {}
        },
        type: ''
      };

      if (detailData.staff) {
        // 'direct' case: process staff list, group by positions
        groupedData.type = 'direct';
        let totalStaff = detailData.staff.length;
        groupedData.AM.total = totalStaff;
        groupedData.PM.total = totalStaff;

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

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

          // PM
          if (!groupedData.PM.teams[position]) {
            groupedData.PM.teams[position] = { teamName: position, inOffice: 0, wfh: 0, total: 0, staff: [] };
          }
          groupedData.PM.teams[position].total += 1;
          if (staff.status_pm === 'OFFICE') {
            groupedData.PM.teams[position].inOffice += 1;
            groupedData.PM.inOffice += 1;
          } else {
            groupedData.PM.teams[position].wfh += 1;
            groupedData.PM.wfh += 1;
          }
          groupedData.PM.teams[position].staff.push({ staff_id: staff.staff_id, name: staff.name, status: staff.status_pm });
        });
      } else if (detailData.managers) {
        // 'manager' case
        groupedData.type = 'manager';

        // For each manager
        for (let managerTeamName in detailData.managers) {
          const managerData = detailData.managers[managerTeamName];
          const managerName = managerData.manager_name;
          const managerPosition = managerData.manager_position || 'Unknown';
          const staffList = managerData.staff;

          // Initialize team for manager
          ['AM', 'PM'].forEach(period => {
            if (!groupedData[period].teams[managerName]) {
              groupedData[period].teams[managerName] = { teamName: managerName, position: managerPosition, inOffice: 0, wfh: 0, total: 0, subTeams: {} };
            }
          });

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

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

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

      selectedDateDetails.value = groupedData;
      expandedTeams.value = {}; // Reset expanded teams

      // Show the card
      isRightContainerVisible.value = true;
    } else {
      alert('Error fetching details for the selected date.');
    }
  }
  
  // Fetch manager schedule details for a specific 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;
    }
  }
  
  // Initiate loading of summary data in chunks
  async function initiateChunkedSummaryLoading() {
    isLoading.value = true;
    const today = new Date();
  
    // Define the date ranges for current month and surrounding months
    const ranges = [];
  
    // Current month
    const currentMonthStart = new Date(today.getFullYear(), today.getMonth(), 1);
    const currentMonthEnd = new Date(today.getFullYear(), today.getMonth() + 1, 0);
    ranges.push({ start: new Date(currentMonthStart), end: new Date(currentMonthEnd) });
  
  
    // Months before current month (2 months)
    for (let i = 1; i <= 2; i++) {
      const start = new Date(today.getFullYear(), today.getMonth() - i, 1);
      const end = new Date(today.getFullYear(), today.getMonth() - i + 1, 0);
      ranges.push({ start, end });
    }
  
    // Months after current month (3 months)
    for (let i = 1; i <= 3; i++) {
      const start = new Date(today.getFullYear(), today.getMonth() + i, 1);
      const end = new Date(today.getFullYear(), today.getMonth() + i + 1, 0);
      ranges.push({ start, end });
    }
  
    // Function to load each range sequentially
    for (let i = 0; i < ranges.length; i++) {
      // For the first range (current month), load immediately
      // For others, load in the background with a slight delay
      if (i === 0) {
        await fetchManagerScheduleSummary(ranges[i].start, ranges[i].end);
      } else {
        setTimeout(async () => {
          await fetchManagerScheduleSummary(ranges[i].start, ranges[i].end);
        }, i * 1000); // 1-second interval between requests
      }
    }
  
    isLoading.value = false;
  }
  
  // Toggle the expansion state of a team card
  function toggleTeam(timeOfDay, teamName) {
    const key = `${timeOfDay}-${teamName}`;
    expandedTeams.value[key] = !expandedTeams.value[key];
  }
  
  // Toggle the expansion state of a sub-team card
  function toggleSubTeam(timeOfDay, teamName, subTeamName) {
    const key = `${timeOfDay}-${teamName}-${subTeamName}`;
    expandedTeams.value[key] = !expandedTeams.value[key];
  }
  </script>
  
  <style scoped>
  /* The styles remain the same as in ManagerTeamScheduleView.vue, you can reuse them */
  .dashboard-container {
    display: flex;
    flex-direction: column;
    height: 100vh;
    overflow: hidden;
  }
  
  /* ... (Include all the styles from ManagerTeamScheduleView.vue) */
  

.dashboard-container {
  display: flex;
  flex-direction: column;
  height: 100vh;
  overflow: hidden;
}

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

.calendar-container {
  width: 70%;
  padding: 1rem;
  flex: 1;
  min-width: 300px;
  overflow-y: auto;
  position: relative;
}

.right-container {
  width: 30%;
  padding: 1.5rem;
  background-color: #f4f6f8;
  border-left: 1px solid #dfe3e8;
  overflow-y: auto;
  flex: none;
  min-width: 300px;
  height: 100%;
  position: relative;
  transition: transform 0.3s ease-in-out;
}

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

.details-container {
  display: flex;
  flex-direction: column;
}

.details-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1.5rem;
}

.details-header h2 {
  margin: 0;
  font-size: 1.75rem;
  color: #2e3a59;
}

.close-button {
  background: none;
  border: none;
  font-size: 1.75rem;
  cursor: pointer;
  color: #888;
}

.tabs {
  display: flex;
  margin-bottom: 1rem;
}

.tab {
  flex: 1;
  padding: 0.75rem 1rem;
  background-color: #e0e7ff;
  border: none;
  cursor: pointer;
  font-weight: 600;
  color: #141b4d;
  transition: background-color 0.3s ease;
}

.tab:not(:last-child) {
  border-right: 1px solid #c7d2fe;
}

.tab.active {
  background-color: #141b4d;
  color: #ffffff;
}

.tab:hover {
  background-color: #c7d2fe;
}

.tab-content {
  flex: 1;
}

.summary {
  display: flex;
  justify-content: space-between;
  margin-bottom: 1rem;
}

.summary-item {
  display: flex;
  align-items: center;
}

.summary-label {
  font-weight: 600;
  margin-right: 0.5rem;
  color: #2e3a59;
}

.summary-value {
  font-weight: 500;
  color: #141b4d;
}

.teams {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.team {
  background-color: #ffffff;
  border: 1px solid #dfe3e8;
  border-radius: 8px;
  padding: 1rem;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
  cursor: pointer; /* Indicate that the card is clickable */
}

.team-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 0.75rem;
}

.team-header h3 {
  margin: 0;
  font-size: 1rem;
  color: #2e3a59;
}

.badge-container {
  display: flex;
  gap: 0.5rem;
}

.badge {
  display: inline-block;
  padding: 0.25rem 0.75rem;
  border-radius: 12px;
  font-size: 0.85em;
  color: #ffffff;
}

.badge-office {
  background-color: #6BCB77; /* Green */
}

.badge-wfh {
  background-color: #FFD93D; /* Yellow */
  color: #000000; /* Black text for better visibility */
}

.staff-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.staff-card {
  display: flex;
  align-items: center;
  border: 1px solid #dfe3e8;
  border-radius: 6px;
  padding: 0.5rem 0.75rem;
  background-color: #f9fafb;
}

.status-indicator {
  display: inline-block;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  margin-right: 0.75rem;
}

.status-indicator.green {
  background-color: #6BCB77;
}

.status-indicator.yellow {
  background-color: #FFD93D;
}

.staff-name {
  font-weight: 500;
  color: #2e3a59;
  font-size: 0.8rem;
}

.no-selection {
  text-align: center;
  color: #888;
  margin-top: 2rem;
  font-size: 1.1rem;
}

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

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

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

.calendar-event {
  /* Additional styling if needed */
}

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

/* Adjust the calendar height */
.fc .fc-view-harness {
  min-height: 0;
}

/* Hide the scrollbar but allow scrolling */
.calendar-container::-webkit-scrollbar {
  width: 0;
  height: 0;
}

.right-container::-webkit-scrollbar {
  width: 8px;
}

.right-container::-webkit-scrollbar-thumb {
  background-color: rgba(0, 0, 0, 0.2);
  border-radius: 4px;
}

.right-container::-webkit-scrollbar-track {
  background-color: transparent;
}

/* Media query for responsive layout */
@media (max-width: 768px) {
  .calendar-container {
    width: 100%;
  }

  .right-container {
    position: fixed; /* Change to fixed for overlay */
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    background-color: rgba(255, 255, 255, 0.95); /* Semi-transparent background */
    transform: translateX(100%);
    transition: transform 0.3s ease-in-out;
    z-index: 1000;
    overflow-y: auto;
    padding: 1.5rem;
  }

  .right-container.show {
    transform: translateX(0);
  }

  /* Ensure text within badges remains black on yellow backgrounds */
  .badge-wfh, .badge-wfh-calendar {
    color: #000000 !important;
  }
}

@media (max-width: 768px) {
  .main-section {
    flex-direction: column;
  }

  .right-container {
    width: 100%;
    min-width: unset;
    height: auto;
    position: fixed; /* Ensure it overlays */
    right: 0;
    top: 0;
    box-shadow: none;
    background-color: #ffffff;
  }

  .calendar-container {
    width: 100%;
    height: 100%;
  }

  .details-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
  }

  .tabs {
    flex-direction: row;
    justify-content: space-between;
  }
}

/* New Styles for Conditional Text Colors */
.yellow-event {
  color: #000000 !important;
}

.default-event {
  color: #ffffff !important;
}

.yellow-event .badge,
.default-event .badge {
  color: inherit !important;
}

/* Loading Overlay Styles */
.loading-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(255, 255, 255, 0.8);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 10;
}

.spinner {
  border: 8px solid #f3f3f3; /* Light grey */
  border-top: 8px solid #3498db; /* Blue */
  border-radius: 50%;
  width: 60px;
  height: 60px;
  animation: spin 2s linear infinite;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

/* Indicate that team cards are clickable */
.team.card {
  transition: background-color 0.2s ease;
}

.team.card:hover {
  background-color: #f0f4ff;
}

  /* Additional styles for sub-teams */
  .sub-team {
    background-color: #ffffff;
    border: 1px solid #dfe3e8;
    border-radius: 8px;
    padding: 0.75rem;
    margin-left: 1rem; /* Indent sub-teams */
    cursor: pointer;
    margin-bottom: 1rem;
  }
  
  .sub-team .team-header h4 {
    margin: 0;
    font-size: 1rem;
    color: #2e3a59;
  }

  </style>
  

File: frontend\src\views\HRScheduleView.vue
--------------------------------------------------------------------------------
<template>
  <div id="app">
    <Navbar />
    <div class="dashboard-container">
      <div class="main-section">
        <div class="calendar-container">
          <FullCalendar :options="calendarOptions" @dateClick="handleDateClick" />
          <div v-if="isLoading" class="loading-overlay">
            <div class="spinner"></div>
          </div>
        </div>
        <div class="right-container" :class="{'show': isRightContainerVisible}">
          <div v-if="selectedDateDetails" class="details-container">
            <div class="details-header">
              <h2>Details for {{ selectedDate }}</h2>
              <button 
                v-if="isSmallScreen" 
                class="close-button" 
                @click="isRightContainerVisible = false"
              >
                ×
              </button>
            </div>
            <div class="tabs">
              <button :class="['tab', activeTab === 'AM' ? 'active' : '']" @click="activeTab = 'AM'">AM</button>
              <button :class="['tab', activeTab === 'PM' ? 'active' : '']" @click="activeTab = 'PM'">PM</button>
            </div>
            <!-- filter department dropdown -->
            <select class="custom-dropdown" v-model="selectedDepartment" @change="filterByDepartment">
              <option value="" disabled selected>Select Department</option>
              <option v-for="department in departments" :key="department.dept" :value="department.dept">
              {{ department.dept }}
              </option>
          </select>
            <div class="tab-content">
              <div v-if="activeTab === 'AM'">
                <div class="summary" v-if="selectedDepartment">
                  <div class="summary-item">
                    <span class="summary-label">In Office:</span>
                    <span class="summary-value">{{ selectedDateDetails.AM.inOffice }} / {{ selectedDateDetails.AM.total }}</span>
                  </div>
                  <div class="summary-item">
                    <span class="summary-label">WFH:</span>
                    <span class="summary-value">{{ selectedDateDetails.AM.wfh }} / {{ selectedDateDetails.AM.total }}</span>
                  </div>
                </div>
                <div class="managers" v-if="Object.keys(selectedDateDetails.AM.managers).length">
                  <div
                    class="manager card"
                    v-for="(manager, managerId) in selectedDateDetails.AM.managers"
                    :key="managerId"
                    @click="toggleManager('AM', managerId)"
                  >
                    <div class="manager-header">
                      <h3>{{ manager.managerName  + "'s team"}}</h3>
                      <div class="badge-container">
                        <span class="badge badge-office">
                          In Office: {{ manager.inOffice }}/{{ manager.total }}
                        </span>
                        <span class="badge badge-wfh">
                          WFH: {{ manager.wfh }}/{{ manager.total }}
                        </span>
                      </div>
                    </div>

                    <div class="position-list" v-if="expandedManagers['AM-' + managerId]">
                      <div
                        class="position card"
                        v-for="(position, positionName) in manager.positions"
                        :key="positionName"
                        @click.stop="togglePosition('AM', managerId, positionName)"
                      >
                        <div class="position-header">
                          <h3>{{ positionName }}</h3>
                          <div class="badge-container">
                            <span class="badge badge-office">
                              In Office: {{ position.staff.filter(s => s.status === 'OFFICE').length }}/{{ position.staff.length }}
                            </span>
                            <span class="badge badge-wfh">
                              WFH: {{ position.staff.filter(s => s.status !== 'OFFICE').length }}/{{ position.staff.length }}
                            </span>
                          </div>
                        </div>

                        <div class="employee-list" v-if="expandedPositions['AM-' + managerId + '-' + positionName]">
                          <div class="employee-card" v-for="staff in position.staff" :key="staff.staff_id">
                            <span :class="['status-indicator', staff.status === 'OFFICE' ? 'green' : 'yellow']"></span>
                            <span class="employee-name">{{ staff.name }}</span>
                          </div>
                        </div>
                      </div>
                    </div>
                  </div>
                </div>

                <div class="teams" v-else>
                  <div 
                    class="team card" 
                    v-for="team in selectedDateDetails.AM.teams" 
                    :key="team.teamName"
                    @click="toggleTeam('AM', team.teamName)"
                  >
                    <div class="team-header">
                      <h3>{{ team.teamName }}</h3>
                      <div class="badge-container">
                        <span class="badge badge-office">
                          In Office: {{ team.inOffice }}/{{ team.total }}
                        </span>
                        <span class="badge badge-wfh">
                          WFH: {{ team.wfh }}/{{ team.total }}
                        </span>
                      </div>
                    </div>
                    <div class="staff-list" v-if="expandedTeams['AM-' + team.teamName]">
                      <div class="staff-card" v-for="staff in team.staff" :key="staff.staff_id">
                        <span :class="['status-indicator', staff.status === 'OFFICE' ? 'green' : 'yellow']"></span>
                        <span class="staff-name">{{ staff.name }}</span>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
              <div v-else-if="activeTab === 'PM'">
                <div class="summary">
                  <div class="summary-item">
                    <span class="summary-label">In Office:</span>
                    <span class="summary-value">{{ selectedDateDetails.PM.inOffice }} / {{ selectedDateDetails.PM.total }}</span>
                  </div>
                  <div class="summary-item">
                    <span class="summary-label">WFH:</span>
                    <span class="summary-value">{{ selectedDateDetails.PM.wfh }} / {{ selectedDateDetails.PM.total }}</span>
                  </div>
                </div>
                <div class="managers" v-if="Object.keys(selectedDateDetails.PM.managers).length">
                  <div
                    class="manager card"
                    v-for="(manager, managerId) in selectedDateDetails.PM.managers"
                    :key="managerId"
                    @click="toggleManager('PM', managerId)"
                  >
                    <div class="manager-header">
                      <h3>{{ manager.managerName }}</h3>
                      <div class="badge-container">
                        <span class="badge badge-office">
                          In Office: {{ manager.inOffice }}/{{ manager.total }}
                        </span>
                        <span class="badge badge-wfh">
                          WFH: {{ manager.wfh }}/{{ manager.total }}
                        </span>
                      </div>
                    </div>

                    <div class="position-list" v-if="expandedManagers['PM-' + managerId]">
                      <div
                        class="position card"
                        v-for="(position, positionName) in manager.positions"
                        :key="positionName"
                        @click.stop="togglePosition('PM', managerId, positionName)"
                      >
                        <div class="position-header">
                          <h3>{{ positionName }}</h3>
                          <div class="badge-container">
                            <span class="badge badge-office">
                              In Office: {{ position.staff.filter(s => s.status === 'OFFICE').length }}/{{ position.staff.length }}
                            </span>
                            <span class="badge badge-wfh">
                              WFH: {{ position.staff.filter(s => s.status !== 'OFFICE').length }}/{{ position.staff.length }}
                            </span>
                          </div>
                        </div>

                        <div class="employee-list" v-if="expandedPositions['PM-' + managerId + '-' + positionName]">
                          <div class="employee-card" v-for="staff in position.staff" :key="staff.staff_id">
                            <span :class="['status-indicator', staff.status === 'OFFICE' ? 'green' : 'yellow']"></span>
                            <span class="employee-name">{{ staff.name }}</span>
                          </div>
                        </div>
                      </div>
                    </div>
                  </div>
                </div>

                <div class="teams" v-else>
                  <div 
                    class="team card" 
                    v-for="team in selectedDateDetails.PM.teams" 
                    :key="team.teamName"
                    @click="toggleTeam('PM', team.teamName)"
                  >
                    <div class="team-header">
                      <h3>{{ team.teamName }}</h3>
                      <div class="badge-container">
                        <span class="badge badge-office">
                          In Office: {{ team.inOffice }}/{{ team.total }}
                        </span>
                        <span class="badge badge-wfh">
                          WFH: {{ team.wfh }}/{{ team.total }}
                        </span>
                      </div>
                    </div>
                    <div class="staff-list" v-if="expandedTeams['PM-' + team.teamName]">
                      <div class="staff-card" v-for="staff in team.staff" :key="staff.staff_id">
                        <span :class="['status-indicator', staff.status === 'OFFICE' ? 'green' : 'yellow']"></span>
                        <span class="staff-name">{{ staff.name }}</span>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
          <div v-else class="no-selection">
            <p>Select a date on the calendar to view details.</p>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

  <script setup>
  import { ref, onMounted, computed, onUnmounted } 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';
  
  // Reactive references
  const user = ref({});
  const selectedDate = ref('');
  const events = ref([]);
  const selectedDateDetails = ref(null);
  const isRightContainerVisible = ref(false);
  const isSmallScreen = ref(window.innerWidth < 768); // Reactive property for screen size
  const isLoading = ref(false); // Loading indicator for summary loading
  const activeTab = ref('AM'); // Active tab state
  const expandedTeams = ref({}); // Tracks expanded state of team cards
  const departments = ref([]); // List of departments fetched from the backend
  const filteredStaff = ref([]); // Store filtered events
  const showFilter = ref(false); // Control visibility of the filter dropdown
  const selectedDepartment = ref(''); // Selected department from the dropdown
  const selectedDepartmentName = ref(''); // To display selected department name
  const expandedManagers = ref([]); // Initialize as an empty object
  const expandedPositions = ref([]); // Initialize as an empty object
  // Handle window resize to update isSmallScreen
  const handleResize = () => {
    isSmallScreen.value = window.innerWidth < 768;
  };
  
  onMounted(() => {
    const storedUser = localStorage.getItem('user');
    if (storedUser) {
      user.value = JSON.parse(storedUser);
      if (user.value.role === 1) {
        initiateChunkedSummaryLoading();
        fetchDepartments();

      }
    }
    window.addEventListener('resize', handleResize);
  });
  
  onUnmounted(() => {
    window.removeEventListener('resize', handleResize);
  });
  
  // Compute minimum and maximum selectable dates
  const computeMinDate = computed(() => {
    const date = new Date();
    date.setMonth(date.getMonth() - 2);
    return date;
  });
  
  const computeMaxDate = computed(() => {
    const date = new Date();
    date.setMonth(date.getMonth() + 3);
    return date;
  });
  
  // Determine color based on percentage
  function getColorForPercentage(percentage) {
    percentage = parseFloat(percentage);
    if (percentage < 50) {
      return '#FF6B6B'; // Red
    } else if (percentage < 75) {
      return '#FFD93D'; // Yellow
    } else {
      return '#6BCB77'; // Green
    }
  }
  
  // Fetch summary data for a given date range
  async function fetchHRScheduleSummary(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/hr-schedule-summary`, {
        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) In Office: ${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) In Office: ${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 = [...events.value, ...newEvents];
    } catch (error) {
      console.error('Error fetching HR schedule summary:', error);
    }
  }
  
  // Format date to YYYY-MM-DD
  function getDateStr(dateObj) {
    const year = dateObj.getFullYear();
    const month = ('0' + (dateObj.getMonth() + 1)).slice(-2);
    const day = ('0' + dateObj.getDate()).slice(-2);
    return `${year}-${month}-${day}`;
  }
  
  // Calendar options
  const calendarOptions = computed(() => {
    let smallScreen = isSmallScreen.value;
  
    return {
      plugins: [dayGridPlugin, timeGridPlugin, interactionPlugin],
      initialView: 'dayGridMonth',
      headerToolbar: {
        left: 'prev,next today',
        center: 'title',
        right: 'dayGridMonth,timeGridWeek,timeGridDay',
      },
      dateClick: handleDateClick,
      eventClick: handleEventClick,
      datesSet: handleDatesSet,
      selectable: true,
      validRange: {
        start: computeMinDate.value,
        end: computeMaxDate.value,
      },
      weekends: false,
      events: events.value,
      allDaySlot: false,
      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.flexDirection = 'column';
          container.style.justifyContent = 'center';
          container.style.alignItems = 'flex-start'; // Align badges beside text
          container.style.backgroundColor = arg.event.backgroundColor;
  
          // Assign a class based on background color
          if (arg.event.backgroundColor === '#FFD93D') { // Yellow
            container.classList.add('yellow-event');
          } else {
            container.classList.add('default-event');
          }
  
          container.style.borderRadius = '4px';
          container.style.margin = '1px 0';
          container.style.padding = '2px 4px';
          container.style.fontSize = '0.8em';
          container.style.width = '100%';
          container.style.height = '100%';
  
          // Create a wrapper for the label and badges
          const labelContainer = document.createElement('div');
          labelContainer.style.display = 'flex';
          labelContainer.style.alignItems = 'center';
          labelContainer.style.width = '100%';
          labelContainer.style.justifyContent = 'space-between';
  
          const label = document.createElement('span');
          label.textContent = `${timeOfDay}`;
  
          const badgesWrapper = document.createElement('div');
          badgesWrapper.style.display = 'flex';
          badgesWrapper.style.gap = '4px';
  
          // Create Office Badge
          const officeBadge = document.createElement('span');
          officeBadge.classList.add('badge', 'badge-office-calendar');
          officeBadge.textContent = `In Office: ${officeCount}`;
  
          badgesWrapper.appendChild(officeBadge);
  
          // Conditionally add WFH badge if not small screen
          if (!smallScreen) {
            const wfhBadge = document.createElement('span');
            wfhBadge.classList.add('badge', 'badge-wfh-calendar');
            wfhBadge.textContent = `WFH: ${totalStaff - officeCount}`;
            badgesWrapper.appendChild(wfhBadge);
          }
  
          labelContainer.appendChild(label);
          labelContainer.appendChild(badgesWrapper);
  
          container.appendChild(labelContainer);
  
          return { domNodes: [container] };
        }
        return null;
      },
      eventDidMount: function(info) {
        // Adjust text color based on background
        const bgColor = info.event.backgroundColor.toUpperCase();
        const yellowHex = '#FFD93D';
  
        if (bgColor === yellowHex) {
          // Set text color to black
          info.el.style.color = '#000000';
  
          // Ensure child elements inherit the color
          const elements = info.el.querySelectorAll('*');
          elements.forEach(el => {
            el.style.color = 'inherit';
          });
        } else {
          // Default text color
          info.el.style.color = '#ffffff';
  
          const elements = info.el.querySelectorAll('*');
          elements.forEach(el => {
            el.style.color = 'inherit';
          });
        }
      },
      eventClassNames: 'calendar-event',
      slotMinTime: '09:00:00',
      slotMaxTime: '19:00:00',
      businessHours: {
        startTime: '09:00',
        endTime: '18:00',
        daysOfWeek: [1, 2, 3, 4, 5],
      },
    };
  });
  
  // Handle date click on the calendar
  async function handleDateClick(info) {
    console.log('hi')
    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;
        activeTab.value = 'AM'; // Reset to AM tab on new selection
        if (user.value.role === 1) {
            await displayDateDetails(info.dateStr);
        }
        }
    }
    }

  
  // Handle event click on the calendar
  async function handleEventClick(info) {
    if (user.value.role === 1) {
      const dateStr = getDateStr(info.event.start);
      const viewType = info.view.type;
      if (viewType === 'timeGridWeek' || viewType === 'timeGridDay') {
        selectedDate.value = dateStr;
        activeTab.value = 'AM'; // Reset to AM tab on new selection
        await displayDateDetails(dateStr);
      }
    }
  }
  
  // Handle when dates are set in the calendar view
  async function handleDatesSet(info) {
    if (info.view.type === 'timeGridDay') {
      const dateStr = getDateStr(info.start);
      selectedDate.value = dateStr;
      activeTab.value = 'AM'; // Reset to AM tab on dates set
      if (user.value.role === 1) {
        const detailData = await fetchHRScheduleDetail(dateStr);
        if (detailData) {
          let groupedData = {
            AM: {
              inOffice: 0,
              wfh: 0,
              total: detailData.staff.length,
              teams: {}
            },
            PM: {
              inOffice: 0,
              wfh: 0,
              total: detailData.staff.length,
              teams: {}
            }
          };
  
          detailData.staff.forEach(staff => {
            const position = staff.position || 'Unknown';
  
            // AM
            if (!groupedData.AM.teams[position]) {
              groupedData.AM.teams[position] = { teamName: position, inOffice: 0, wfh: 0, total: 0, staff: [] };
            }
            groupedData.AM.teams[position].total += 1;
            if (staff.status_am === 'OFFICE') {
              groupedData.AM.teams[position].inOffice += 1;
              groupedData.AM.inOffice += 1;
            } else {
              groupedData.AM.teams[position].wfh += 1;
              groupedData.AM.wfh += 1;
            }
            groupedData.AM.teams[position].staff.push({ staff_id: staff.staff_id, name: staff.name, status: staff.status_am });
  
            // PM
            if (!groupedData.PM.teams[position]) {
              groupedData.PM.teams[position] = { teamName: position, inOffice: 0, wfh: 0, total: 0, staff: [] };
            }
            groupedData.PM.teams[position].total += 1;
            if (staff.status_pm === 'OFFICE') {
              groupedData.PM.teams[position].inOffice += 1;
              groupedData.PM.inOffice += 1;
            } else {
              groupedData.PM.teams[position].wfh += 1;
              groupedData.PM.wfh += 1;
            }
            groupedData.PM.teams[position].staff.push({ staff_id: staff.staff_id, name: staff.name, status: staff.status_pm });
          });
  
          groupedData.AM.total = detailData.staff.length;
          groupedData.PM.total = detailData.staff.length;
  
          selectedDateDetails.value = groupedData;
          expandedTeams.value = {}; // Reset expanded teams
  
          // Only show the card on large screens when navigating dates
          if (!isSmallScreen.value) {
            isRightContainerVisible.value = true;
          }
          // On small screens, do not automatically show the card when navigating
        } else {
          alert('Error fetching details for the selected date.');
        }
      }
    }
  }
  async function filterByDepartment() {
  if (!selectedDepartment.value) return;

  await displayDateDetails(selectedDate.value);
}
  async function displayDateDetails(dateStr) {
    const detailData = await fetchHRScheduleDetail(dateStr);

    if (detailData) {
      const filteredStaff = detailData.staff.filter(staff => staff.dept === selectedDepartment.value);
      
      let groupedData = {
        AM: {
          inOffice: 0,
          wfh: 0,
          total: filteredStaff.length,
          managers: {},
          teams: {}
        },
        PM: {
          inOffice: 0,
          wfh: 0,
          total: filteredStaff.length,
          managers: {},
          teams: {}
        }
      };

      const hasManagers = filteredStaff.some(staff => staff.role === 3);

      const processStaff = (period) => {
        filteredStaff.forEach(staff => {
          const position = staff.position || 'Unknown';
          const role = staff.role;

          if (hasManagers) { 

            if (role === 3 || role === 1) {
              if (!groupedData[period].managers[staff.staff_id]) {
                groupedData[period].managers[staff.staff_id] = {
                  managerId: staff.staff_id,
                  managerName: staff.name,
                  inOffice: 0,
                  wfh: 0,
                  total: 0,
                  positions: {}
                };
              }


              // if (staff[`status_${period.toLowerCase()}`] === 'OFFICE') {
              //   groupedData[period].managers[staff.staff_id].inOffice += 1;
              //   groupedData[period].inOffice += 1;
              // } else {
              //   groupedData[period].managers[staff.staff_id].wfh += 1;
              //   groupedData[period].wfh += 1;
              // }
              // groupedData[period].managers[staff.staff_id].total += 1;
            }

            if (staff.role === 2){
            if (staff.manager_id in groupedData[period].managers) {
              if (staff[`status_${period.toLowerCase()}`] === 'OFFICE') {
                groupedData[period].managers[staff.manager_id].inOffice += 1;
                groupedData[period].inOffice += 1;
              } else {
                groupedData[period].managers[staff.manager_id].wfh += 1;
                groupedData[period].wfh += 1;
              }
              groupedData[period].managers[staff.manager_id].total += 1;

              if (!groupedData[period].managers[staff.manager_id].positions[position]) {
                groupedData[period].managers[staff.manager_id].positions[position] = { staff: [] };
              }
              groupedData[period].managers[staff.manager_id].positions[position].staff.push({
                staff_id: staff.staff_id,
                name: staff.name,
                status: staff[`status_${period.toLowerCase()}`]
              });
            }}else{
            if (staff.staff_id in groupedData[period].managers) {
              console.log(groupedData[period].managers)
              if (staff[`status_${period.toLowerCase()}`] === 'OFFICE') {
                groupedData[period].managers[staff.staff_id].inOffice += 1;
                groupedData[period].inOffice += 1;
              } else {
                groupedData[period].managers[staff.staff_id].wfh += 1;
                groupedData[period].wfh += 1;
              }
              groupedData[period].managers[staff.staff_id].total += 1;

              if (!groupedData[period].managers[staff.staff_id].positions[position]) {
                groupedData[period].managers[staff.staff_id].positions[position] = { staff: [] };
              }
              groupedData[period].managers[staff.staff_id].positions[position].staff.push({
                staff_id: staff.staff_id,
                name: staff.name,
                status: staff[`status_${period.toLowerCase()}`]
              });
            }
          }
        }else { 
            if (!groupedData[period].teams[position]) {
              groupedData[period].teams[position] = { teamName: position, inOffice: 0, wfh: 0, total: 0, staff: [] };
            }
            groupedData[period].teams[position].total += 1;

            if (staff[`status_${period.toLowerCase()}`] === 'OFFICE') {
              groupedData[period].teams[position].inOffice += 1;
              groupedData[period].inOffice += 1;
            } else {
              groupedData[period].teams[position].wfh += 1;
              groupedData[period].wfh += 1;
            }
            groupedData[period].teams[position].staff.push({
              staff_id: staff.staff_id,
              name: staff.name,
              status: staff[`status_${period.toLowerCase()}`]
            });
          }});
      };

      processStaff('AM');
      processStaff('PM');

      selectedDateDetails.value = groupedData;
      expandedTeams.value = {}; 

      isRightContainerVisible.value = true;
    } else {
      alert('Error fetching details for the selected date.');
    }
  }
  
  // Fetch HR schedule details for a specific date
  async function fetchHRScheduleDetail(dateStr) {
    try {
      const response = await axios.get(`${import.meta.env.VITE_API_URL}/api/hr-schedule-detail/${dateStr}`);
      return response.data;
    } catch (error) {
      console.error('Error fetching HR schedule detail:', error);
      return null;
    }
  }
  const fetchDepartments = async () => {
    try {
        const response = await axios.get(`${import.meta.env.VITE_API_URL}/api/departments`);
        departments.value = response.data.departments; // Assuming the response data is an array of departments
        console.log(departments.value); // Check the console for the departments
    } catch (error) {
        console.error('Error fetching departments:', error);
    }
    };


  // Initiate loading of summary data in chunks
  async function initiateChunkedSummaryLoading() {
    isLoading.value = true;
    const today = new Date();
  
    // Define the date ranges for current month and surrounding months
    const ranges = [];
  
    // Current month
    const currentMonthStart = new Date(today.getFullYear(), today.getMonth(), 1);
    const currentMonthEnd = new Date(today.getFullYear(), today.getMonth() + 1, 0);
    ranges.push({ start: new Date(currentMonthStart), end: new Date(currentMonthEnd) });
  
  
    // Months before current month (2 months)
    for (let i = 1; i <= 2; i++) {
      const start = new Date(today.getFullYear(), today.getMonth() - i, 1);
      const end = new Date(today.getFullYear(), today.getMonth() - i + 1, 0);
      ranges.push({ start, end });
    }
  
    // Months after current month (3 months)
    for (let i = 1; i <= 3; i++) {
      const start = new Date(today.getFullYear(), today.getMonth() + i, 1);
      const end = new Date(today.getFullYear(), today.getMonth() + i + 1, 0);
      ranges.push({ start, end });
    }
  
    // Function to load each range sequentially
    for (let i = 0; i < ranges.length; i++) {
      // For the first range (current month), load immediately
      // For others, load in the background with a slight delay
      if (i === 0) {
        await fetchHRScheduleSummary(ranges[i].start, ranges[i].end);
      } else {
        setTimeout(async () => {
          await fetchHRScheduleSummary(ranges[i].start, ranges[i].end);
        }, i * 1000); // 1-second interval between requests
      }
    }
  
    isLoading.value = false;
  }
  
  // Toggle the expansion state of a team card
  function toggleTeam(timeOfDay, teamName) {
    const key = `${timeOfDay}-${teamName}`;
    expandedTeams.value[key] = !expandedTeams.value[key];
  }
  function toggleManager(timeOfDay, managerId) {
  const key = `${timeOfDay}-${managerId}`;
  expandedManagers.value[key] = !expandedManagers.value[key];

  // If a manager is expanded, collapse all positions under it by default
  if (!expandedManagers.value[key]) {
    const manager = selectedDateDetails.value[timeOfDay].managers[managerId];
    Object.keys(manager.positions).forEach(positionName => {
      const positionKey = `${timeOfDay}-${managerId}-${positionName}`;
      expandedPositions.value[positionKey] = false; // Ensure positions are collapsed
    });
  }
}
function togglePosition(timeOfDay, managerId, positionName) {
  const key = `${timeOfDay}-${managerId}-${positionName}`;
  
  // Toggle the staff list visibility for that position
  expandedPositions.value[key] = !expandedPositions.value[key];
}



  </script>
  
  <style scoped>
  .dashboard-container {
    display: flex;
    flex-direction: column;
    height: 100vh;
    overflow: hidden;
  }
  
  .main-section {
    display: flex;
    height: 100%;
    overflow: hidden;
  }
  
  .calendar-container {
    width: 70%;
    padding: 1rem;
    flex: 1;
    min-width: 300px;
    overflow-y: auto;
    position: relative;
  }
  
  .right-container {
    width: 30%;
    padding: 1.5rem;
    background-color: #f4f6f8;
    border-left: 1px solid #dfe3e8;
    overflow-y: auto;
    flex: none;
    min-width: 300px;
    height: 100%;
    position: relative;
    transition: transform 0.3s ease-in-out;
  }
  
  .right-container.show {
    display: block;
  }
  
  .details-container {
    display: flex;
    flex-direction: column;
  }
  
  .details-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 1.5rem;
  }
  
  .details-header h2 {
    margin: 0;
    font-size: 1.75rem;
    color: #2e3a59;
  }
  
  .close-button {
    background: none;
    border: none;
    font-size: 1.75rem;
    cursor: pointer;
    color: #888;
  }
  
  .tabs {
    display: flex;
    margin-bottom: 1rem;
  }
  
  .tab {
    flex: 1;
    padding: 0.75rem 1rem;
    background-color: #e0e7ff;
    border: none;
    cursor: pointer;
    font-weight: 600;
    color: #141b4d;
    transition: background-color 0.3s ease;
  }
  
  .tab:not(:last-child) {
    border-right: 1px solid #c7d2fe;
  }
  
  .tab.active {
    background-color: #141b4d;
    color: #ffffff;
  }
  
  .tab:hover {
    background-color: #c7d2fe;
  }
  
  .tab-content {
    flex: 1;
  }
  
  .summary {
    display: flex;
    justify-content: space-between;
    margin-bottom: 1rem;
  }
  
  .summary-item {
    display: flex;
    align-items: center;
  }
  
  .summary-label {
    font-weight: 600;
    margin-right: 0.5rem;
    color: #2e3a59;
  }
  
  .summary-value {
    font-weight: 500;
    color: #141b4d;
  }
  
  .teams {
    display: flex;
    flex-direction: column;
    gap: 1rem;
  }
  
  .team {
    background-color: #ffffff;
    border: 1px solid #dfe3e8;
    border-radius: 8px;
    padding: 1rem;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
    cursor: pointer; /* Indicate that the card is clickable */
  }
  
  .team-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 0.75rem;
  }
  
  .team-header h3 {
    margin: 0;
    font-size: 1rem;
    color: #2e3a59;
  }
  
  .manager.card {
    transition: background-color 0.2s ease;
  }

  .manager.card:hover {
    background-color: #f0f4ff;
  }

  .managers {
    display: flex;
    flex-direction: column;
    gap: 1rem;
  }

  .manager {
    background-color: #ffffff;
    border: 1px solid #dfe3e8;
    border-radius: 8px;
    padding: 1rem;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
    cursor: pointer;
  }

  .manager-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 0.75rem;
  }

  .manager-header h3 {
    margin: 0;
    font-size: 1rem;
    color: #2e3a59;
  }

  .badge-container {
    display: flex;
    gap: 0.5rem;
  }
  
  .badge {
    display: inline-block;
    padding: 0.25rem 0.75rem;
    border-radius: 12px;
    font-size: 0.85em;
    color: #ffffff;
  }
  
  .badge-office {
    background-color: #6BCB77; /* Green */
  }
  
  .badge-wfh {
    background-color: #FFD93D; /* Yellow */
    color: #000000; /* Black text for better visibility */
  }
  
  .staff-list {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
  }
  
  .staff-card {
    display: flex;
    align-items: center;
    border: 1px solid #dfe3e8;
    border-radius: 6px;
    padding: 0.5rem 0.75rem;
    background-color: #f9fafb;
  }
  .position.card {
    transition: background-color 0.2s ease;
    gap: 0.5rem
  }

  .position.card:hover {
    background-color: #f0f4ff;
  }

  .position-list {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
  }

  .position {
    background-color: #ffffff;
    border: 1px solid #dfe3e8;
    border-radius: 8px;
    padding: 1rem;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
    cursor: pointer;
  }

  .position-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 0.75rem;
    gap: 0.5rem;
  }

  .position-header h3 {
    margin: 0;
    font-size: 1rem;
    color: #2e3a59;
  }

  
  .status-indicator {
    display: inline-block;
    width: 12px;
    height: 12px;
    border-radius: 50%;
    margin-right: 0.75rem;
  }
  
  .status-indicator.green {
    background-color: #6BCB77;
  }
  
  .status-indicator.yellow {
    background-color: #FFD93D;
  }
  
  .staff-name {
    font-weight: 500;
    color: #2e3a59;
    font-size: 0.8rem;
  }
  
  .no-selection {
    text-align: center;
    color: #888;
    margin-top: 2rem;
    font-size: 1.1rem;
  }
  
  .fc {
    width: 100%;
    height: 100%;
  }
  
  .fc-event-main {
    padding: 2px;
  }
  
  .fc-daygrid-event {
    white-space: normal !important;
  }

  .fc-daygrid-event-harness {
    margin-bottom: 2px !important;
  }
  
  /* Adjust the calendar height */
  .fc .fc-view-harness {
    min-height: 0;
  }
  
  /* Hide the scrollbar but allow scrolling */
  .calendar-container::-webkit-scrollbar {
    width: 0;
    height: 0;
  }
  
  .right-container::-webkit-scrollbar {
    width: 8px;
  }
  
  .right-container::-webkit-scrollbar-thumb {
    background-color: rgba(0, 0, 0, 0.2);
    border-radius: 4px;
  }
  
  .right-container::-webkit-scrollbar-track {
    background-color: transparent;
  }
  
  /* Media query for responsive layout */
  @media (max-width: 768px) {
    .calendar-container {
      width: 100%;
    }
  
    .right-container {
      position: fixed; /* Change to fixed for overlay */
      top: 0;
      right: 0;
      bottom: 0;
      left: 0;
      background-color: rgba(255, 255, 255, 0.95); /* Semi-transparent background */
      transform: translateX(100%);
      transition: transform 0.3s ease-in-out;
      z-index: 1000;
      overflow-y: auto;
      padding: 1.5rem;
    }
  
    .right-container.show {
      transform: translateX(0);
    }
  
    /* Ensure text within badges remains black on yellow backgrounds */
    .badge-wfh, .badge-wfh-calendar {
      color: #000000 !important;
    }
  }
  
  @media (max-width: 768px) {
    .main-section {
      flex-direction: column;
    }
  
    .right-container {
      width: 100%;
      min-width: unset;
      height: auto;
      position: fixed; /* Ensure it overlays */
      right: 0;
      top: 0;
      box-shadow: none;
      background-color: #ffffff;
    }
  
    .calendar-container {
      width: 100%;
      height: 100%;
    }
  
    .details-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
    }
  
    .tabs {
      flex-direction: row;
      justify-content: space-between;
    }
  }
  
  /* New Styles for Conditional Text Colors */
  .yellow-event {
    color: #000000 !important;
  }
  
  .default-event {
    color: #ffffff !important;
  }
  
  .yellow-event .badge,
  .default-event .badge {
    color: inherit !important;
  }
  
  /* Loading Overlay Styles */
  .loading-overlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(255, 255, 255, 0.8);
    display: flex;
    justify-content: center;
    align-items: center;
    z-index: 10;
  }
  
  .spinner {
    border: 8px solid #f3f3f3; /* Light grey */
    border-top: 8px solid #3498db; /* Blue */
    border-radius: 50%;
    width: 60px;
    height: 60px;
    animation: spin 2s linear infinite;
  }
  
  @keyframes spin {
    0% { transform: rotate(0deg); }
    100% { transform: rotate(360deg); }
  }
  
  /* Indicate that team cards are clickable */
  .team.card {
    transition: background-color 0.2s ease;
  }
  
  .team.card:hover {
    background-color: #f0f4ff;
  }

  .employee-list {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
  }

  .employee-card {
    display: flex;
    align-items: center;
    border: 1px solid #dfe3e8;
    border-radius: 6px;
    padding: 0.5rem 0.75rem;
    background-color: #f9fafb;
    transition: background-color 0.2s ease;
    gap: 0.5rem
  }

  .employee-card:hover {
    background-color: #eef3fc;
  }

  .employee-card .status-indicator {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    margin-right: 0.75rem;
  }

  .employee-card .green {
    background-color: #34c759;
  }

  .employee-card .yellow {
    background-color: #ffcc00;
  }

  .employee-card .employee-name {
    font-size: 0.9rem;
    color: #2e3a59;
  }

  .custom-dropdown {
    width: 200px; /* Adjust width */
    padding: 5px;
    margin: 5px;
    border: 2px solid #2e3a59; /* Border color */
    border-radius: 5px; /* Rounded corners */
    background-color: #f9f9f9; /* Background color */
    font-size: 16px; /* Font size */
    color: #333; /* Font color */
    cursor: pointer; /* Change cursor on hover */
  }

  .custom-dropdown:focus {
    outline: none; /* Remove outline */
    border-color: #2e3a59; /* Change border color on focus */
  }
  </style>

File: frontend\src\views\LoginView.vue
--------------------------------------------------------------------------------
<template>
    <div class="login-container">
      <div class="login-form-container">
        <div class="app-title">WFH Scheduler</div>
        <div class="login-form">
          <form @submit.prevent="handleLogin">
            <div class="form-group">
              <label for="email">Email</label>
              <input type="email" id="email" v-model="email" required>
            </div>
            <div class="form-group">
              <label for="password">Password</label>
              <input type="password" id="password" v-model="password" required>
            </div>
            <button type="submit">Log In</button>
          </form>
          <div v-if="errorMessage" class="error-message">
            {{ errorMessage }}
          </div>
        </div>
      </div>
    </div>
  </template>
  
  <script setup>

  import { ref } from 'vue';
  import { useRouter } from 'vue-router';
  import axios from 'axios';
  
  const router = useRouter();
  const email = ref('');
  const password = ref('');
  const errorMessage = ref('');
  
  const handleLogin = async () => {
    try {
      const response = await axios.post(`${import.meta.env.VITE_API_URL}/api/login`, {
        email: email.value,
        password: password.value
      });
  
      if (response.data.staff_id) {
        // Store the user info in localStorage
        
        localStorage.setItem('user', JSON.stringify(response.data));
  
        // Redirect to dashboard
        router.push('/schedule');
      }
    } catch (error) {
      if (error.response) {
        errorMessage.value = error.response.data.message;
      } else {
        errorMessage.value = 'An error occurred. Please try again.';
      }
    }
  };
  </script>
  
  <style scoped>
  .login-container {
    height: 100vh;
    width: 100vw;
    display: flex;
    justify-content: center;
    align-items: center;
    background-image: linear-gradient(rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0.7)), url('@/assets/images/login-image.jpg');
    background-size: cover;
    background-position: center;
  }
  
  .login-form-container {
    background-color: rgba(255, 255, 255, 0.95);
    padding: 2rem;
    border-radius: 15px;
    width: 100%;
    max-width: 400px;
    box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
  }
  
  .app-title {
    font-size: 2.5rem;
    font-weight: bold;
    color: #141b4d;
    margin-bottom: 1.5rem;
    text-align: center;
    text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1);
  }
  
  .login-form h2 {
    text-align: center;
    margin-bottom: 1.5rem;
    color: #141b4d;
  }
  
  .form-group {
    margin-bottom: 1.5rem;
  }
  
  label {
    display: block;
    margin-bottom: 0.5rem;
    color: #141b4d;
    font-weight: 600;
    text-align: left;
  }
  
  input {
    width: 100%;
    padding: 0.75rem;
    border: 1px solid #bdc3c7;
    border-radius: 4px;
    font-size: 1rem;
    transition: border-color 0.3s ease;
  }
  
  input:focus {
    outline: none;
    border-color: #141b4d;
  }
  
  button {
    width: 100%;
    padding: 0.75rem;
    background-color: #141b4d;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 1rem;
    font-weight: 600;
    transition: background-color 0.3s ease;
  }
  
  button:hover {
    background-color: #111d6c;
  }
  
  .error-message {
    color: #e74c3c;
    text-align: center;
    margin-top: 1rem;
  }
  </style>

File: frontend\src\views\ManagerTeamScheduleView.vue
--------------------------------------------------------------------------------
<template>
  <div id="app">
    <Navbar />
    <div class="dashboard-container">
      <div class="main-section">
        <div class="calendar-container">
          <FullCalendar 
            :options="calendarOptions"
          />
          <div v-if="isLoading" class="loading-overlay">
            <div class="spinner"></div>
          </div>
        </div>
        <div class="right-container" :class="{'show': isRightContainerVisible }">
          <div v-if="selectedDateDetails" class="details-container">
            <div class="details-header">
              <h2>Details for {{ selectedDate }}</h2>
              <!-- Close button is only rendered for small screens -->
              <button 
                v-if="isSmallScreen" 
                class="close-button" 
                @click="isRightContainerVisible = false"
              >
                ×
              </button>
            </div>
            <div class="tabs">
              <button 
                :class="['tab', activeTab === 'AM' ? 'active' : '']" 
                @click="activeTab = 'AM'"
              >
                AM
              </button>
              <button 
                :class="['tab', activeTab === 'PM' ? 'active' : '']" 
                @click="activeTab = 'PM'"
              >
                PM
              </button>
            </div>
            <div class="tab-content">
              <div v-if="activeTab === 'AM'">
                <div class="summary">
                  <div class="summary-item">
                    <span class="summary-label">In Office:</span>
                    <span class="summary-value">{{ selectedDateDetails.AM.inOffice }} / {{ selectedDateDetails.AM.total }}</span>
                  </div>
                  <div class="summary-item">
                    <span class="summary-label">WFH:</span>
                    <span class="summary-value">{{ selectedDateDetails.AM.wfh }} / {{ selectedDateDetails.AM.total }}</span>
                  </div>
                </div>
                <div class="teams">
                  <div 
                    class="team card" 
                    v-for="team in selectedDateDetails.AM.teams" 
                    :key="team.teamName"
                    @click="toggleTeam('AM', team.teamName)"
                  >
                    <div class="team-header">
                      <h3>{{ team.teamName }}</h3>
                      <div class="badge-container">
                        <span class="badge badge-office">
                          In Office: {{ team.inOffice }}/{{ team.total }}
                        </span>
                        <span class="badge badge-wfh">
                          WFH: {{ team.wfh }}/{{ team.total }}
                        </span>
                      </div>
                    </div>
                    <div class="staff-list" v-if="expandedTeams['AM-' + team.teamName]">
                      <div class="staff-card" v-for="staff in team.staff" :key="staff.staff_id">
                        <span :class="['status-indicator', staff.status === 'OFFICE' ? 'green' : 'yellow']"></span>
                        <span class="staff-name">{{ staff.name }}</span>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
              <div v-else-if="activeTab === 'PM'">
                <div class="summary">
                  <div class="summary-item">
                    <span class="summary-label">In Office:</span>
                    <span class="summary-value">{{ selectedDateDetails.PM.inOffice }} / {{ selectedDateDetails.PM.total }}</span>
                  </div>
                  <div class="summary-item">
                    <span class="summary-label">WFH:</span>
                    <span class="summary-value">{{ selectedDateDetails.PM.wfh }} / {{ selectedDateDetails.PM.total }}</span>
                  </div>
                </div>
                <div class="teams">
                  <div 
                    class="team card" 
                    v-for="team in selectedDateDetails.PM.teams" 
                    :key="team.teamName"
                    @click="toggleTeam('PM', team.teamName)"
                  >
                    <div class="team-header">
                      <h3>{{ team.teamName }}</h3>
                      <div class="badge-container">
                        <span class="badge badge-office">
                          In Office: {{ team.inOffice }}/{{ team.total }}
                        </span>
                        <span class="badge badge-wfh">
                          WFH: {{ team.wfh }}/{{ team.total }}
                        </span>
                      </div>
                    </div>
                    <div class="staff-list" v-if="expandedTeams['PM-' + team.teamName]">
                      <div class="staff-card" v-for="staff in team.staff" :key="staff.staff_id">
                        <span :class="['status-indicator', staff.status === 'OFFICE' ? 'green' : 'yellow']"></span>
                        <span class="staff-name">{{ staff.name }}</span>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
          <div v-else class="no-selection">
            <p>Select a date on the calendar to view details.</p>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, computed, onUnmounted } 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';

// Reactive references
const user = ref({});
const selectedDate = ref('');
const events = ref([]);
const selectedDateDetails = ref(null);
const isRightContainerVisible = ref(false);
const isSmallScreen = ref(window.innerWidth < 768); // Reactive property for screen size
const isLoading = ref(false); // Loading indicator for summary loading
const activeTab = ref('AM'); // Active tab state
const expandedTeams = ref({}); // Tracks expanded state of team cards

// Handle window resize to update isSmallScreen
const handleResize = () => {
  isSmallScreen.value = window.innerWidth < 768;
};

onMounted(() => {
  const storedUser = localStorage.getItem('user');
  if (storedUser) {
    user.value = JSON.parse(storedUser);
    if (user.value.role === 3) {
      initiateChunkedSummaryLoading();
    }
  }
  window.addEventListener('resize', handleResize);
});

onUnmounted(() => {
  window.removeEventListener('resize', handleResize);
});

// Compute minimum and maximum selectable dates
const computeMinDate = computed(() => {
  const date = new Date();
  date.setMonth(date.getMonth() - 2);
  return date;
});

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

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

// Fetch summary data for a given date range
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) In Office: ${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) In Office: ${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 = [...events.value, ...newEvents];
  } catch (error) {
    console.error('Error fetching manager schedule summary:', error);
  }
}

// Format date to YYYY-MM-DD
function getDateStr(dateObj) {
  const year = dateObj.getFullYear();
  const month = ('0' + (dateObj.getMonth() + 1)).slice(-2);
  const day = ('0' + dateObj.getDate()).slice(-2);
  return `${year}-${month}-${day}`;
}

// Calendar options
const calendarOptions = computed(() => {
  let smallScreen = isSmallScreen.value;

  return {
    plugins: [dayGridPlugin, timeGridPlugin, interactionPlugin],
    initialView: 'dayGridMonth',
    headerToolbar: {
      left: 'prev,next today',
      center: 'title',
      right: 'dayGridMonth,timeGridWeek,timeGridDay',
    },
    dateClick: handleDateClick,
    eventClick: handleEventClick,
    datesSet: handleDatesSet,
    selectable: true,
    validRange: {
      start: computeMinDate.value,
      end: computeMaxDate.value,
    },
    weekends: false,
    events: events.value,
    allDaySlot: false,
    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.flexDirection = 'column';
        container.style.justifyContent = 'center';
        container.style.alignItems = 'flex-start'; // Align badges beside text
        container.style.backgroundColor = arg.event.backgroundColor;

        // Assign a class based on background color
        if (arg.event.backgroundColor === '#FFD93D') { // Yellow
          container.classList.add('yellow-event');
        } else {
          container.classList.add('default-event');
        }

        container.style.borderRadius = '4px';
        container.style.margin = '1px 0';
        container.style.padding = '2px 4px';
        container.style.fontSize = '0.8em';
        container.style.width = '100%';
        container.style.height = '100%';

        // Create a wrapper for the label and badges
        const labelContainer = document.createElement('div');
        labelContainer.style.display = 'flex';
        labelContainer.style.alignItems = 'center';
        labelContainer.style.width = '100%';
        labelContainer.style.justifyContent = 'space-between';

        const label = document.createElement('span');
        label.textContent = `${timeOfDay}`;

        const badgesWrapper = document.createElement('div');
        badgesWrapper.style.display = 'flex';
        badgesWrapper.style.gap = '4px';

        // Create Office Badge
        const officeBadge = document.createElement('span');
        officeBadge.classList.add('badge', 'badge-office-calendar');
        officeBadge.textContent = `In Office: ${officeCount}`;

        badgesWrapper.appendChild(officeBadge);

        // Conditionally add WFH badge if not small screen
        if (!smallScreen) {
          const wfhBadge = document.createElement('span');
          wfhBadge.classList.add('badge', 'badge-wfh-calendar');
          wfhBadge.textContent = `WFH: ${totalStaff - officeCount}`;
          badgesWrapper.appendChild(wfhBadge);
        }

        labelContainer.appendChild(label);
        labelContainer.appendChild(badgesWrapper);

        container.appendChild(labelContainer);

        return { domNodes: [container] };
      }
      return null;
    },
    eventDidMount: function(info) {
      // Adjust text color based on background
      const bgColor = info.event.backgroundColor.toUpperCase();
      const yellowHex = '#FFD93D';

      if (bgColor === yellowHex) {
        // Set text color to black
        info.el.style.color = '#000000';

        // Ensure child elements inherit the color
        const elements = info.el.querySelectorAll('*');
        elements.forEach(el => {
          el.style.color = 'inherit';
        });
      } else {
        // Default text color
        info.el.style.color = '#ffffff';

        const elements = info.el.querySelectorAll('*');
        elements.forEach(el => {
          el.style.color = 'inherit';
        });
      }
    },
    eventClassNames: 'calendar-event',
    slotMinTime: '09:00:00',
    slotMaxTime: '19:00:00',
    businessHours: {
      startTime: '09:00',
      endTime: '18:00',
      daysOfWeek: [1, 2, 3, 4, 5],
    },
  };
});

// Handle date click on the calendar
async function handleDateClick(info) {
  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;
      activeTab.value = 'AM'; // Reset to AM tab on new selection
      if (user.value.role === 3) {
        await displayDateDetails(info.dateStr);
      }
    }
  }
}

// Handle event click on the calendar
async function handleEventClick(info) {
  if (user.value.role === 3) {
    const dateStr = getDateStr(info.event.start);
    const viewType = info.view.type;
    if (viewType === 'timeGridWeek' || viewType === 'timeGridDay') {
      selectedDate.value = dateStr;
      activeTab.value = 'AM'; // Reset to AM tab on new selection
      await displayDateDetails(dateStr);
    }
  }
}

// Handle when dates are set in the calendar view
async function handleDatesSet(info) {
  if (info.view.type === 'timeGridDay') {
    const dateStr = getDateStr(info.start);
    selectedDate.value = dateStr;
    activeTab.value = 'AM'; // Reset to AM tab on dates set
    if (user.value.role === 3) {
      const detailData = await fetchManagerScheduleDetail(dateStr);
      if (detailData) {
        let groupedData = {
          AM: {
            inOffice: 0,
            wfh: 0,
            total: detailData.staff.length,
            teams: {}
          },
          PM: {
            inOffice: 0,
            wfh: 0,
            total: detailData.staff.length,
            teams: {}
          }
        };

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

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

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

        groupedData.AM.total = detailData.staff.length;
        groupedData.PM.total = detailData.staff.length;

        selectedDateDetails.value = groupedData;
        expandedTeams.value = {}; // Reset expanded teams

        // Only show the card on large screens when navigating dates
        if (!isSmallScreen.value) {
          isRightContainerVisible.value = true;
        }
        // On small screens, do not automatically show the card when navigating
      } else {
        alert('Error fetching details for the selected date.');
      }
    }
  }
}

// Display date details when a date is selected
async function displayDateDetails(dateStr) {
  const detailData = await fetchManagerScheduleDetail(dateStr);
  if (detailData) {
    let groupedData = {
      AM: {
        inOffice: 0,
        wfh: 0,
        total: detailData.staff.length,
        teams: {}
      },
      PM: {
        inOffice: 0,
        wfh: 0,
        total: detailData.staff.length,
        teams: {}
      }
    };

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

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

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

    groupedData.AM.total = detailData.staff.length;
    groupedData.PM.total = detailData.staff.length;

    selectedDateDetails.value = groupedData;
    expandedTeams.value = {}; // Reset expanded teams

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

// Fetch manager schedule details for a specific 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;
  }
}

// Initiate loading of summary data in chunks
async function initiateChunkedSummaryLoading() {
  isLoading.value = true;
  const today = new Date();

  // Define the date ranges for current month and surrounding months
  const ranges = [];

  // Current month
  const currentMonthStart = new Date(today.getFullYear(), today.getMonth(), 1);
  const currentMonthEnd = new Date(today.getFullYear(), today.getMonth() + 1, 0);
  ranges.push({ start: new Date(currentMonthStart), end: new Date(currentMonthEnd) });


  // Months before current month (2 months)
  for (let i = 1; i <= 2; i++) {
    const start = new Date(today.getFullYear(), today.getMonth() - i, 1);
    const end = new Date(today.getFullYear(), today.getMonth() - i + 1, 0);
    ranges.push({ start, end });
  }

  // Months after current month (3 months)
  for (let i = 1; i <= 3; i++) {
    const start = new Date(today.getFullYear(), today.getMonth() + i, 1);
    const end = new Date(today.getFullYear(), today.getMonth() + i + 1, 0);
    ranges.push({ start, end });
  }

  // Function to load each range sequentially
  for (let i = 0; i < ranges.length; i++) {
    // For the first range (current month), load immediately
    // For others, load in the background with a slight delay
    if (i === 0) {
      await fetchManagerScheduleSummary(ranges[i].start, ranges[i].end);
    } else {
      setTimeout(async () => {
        await fetchManagerScheduleSummary(ranges[i].start, ranges[i].end);
      }, i * 1000); // 1-second interval between requests
    }
  }

  isLoading.value = false;
}

// Toggle the expansion state of a team card
function toggleTeam(timeOfDay, teamName) {
  const key = `${timeOfDay}-${teamName}`;
  expandedTeams.value[key] = !expandedTeams.value[key];
}
</script>

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

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

.calendar-container {
  width: 70%;
  padding: 1rem;
  flex: 1;
  min-width: 300px;
  overflow-y: auto;
  position: relative;
}

.right-container {
  width: 30%;
  padding: 1.5rem;
  background-color: #f4f6f8;
  border-left: 1px solid #dfe3e8;
  overflow-y: auto;
  flex: none;
  min-width: 300px;
  height: 100%;
  position: relative;
  transition: transform 0.3s ease-in-out;
}

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

.details-container {
  display: flex;
  flex-direction: column;
}

.details-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1.5rem;
}

.details-header h2 {
  margin: 0;
  font-size: 1.75rem;
  color: #2e3a59;
}

.close-button {
  background: none;
  border: none;
  font-size: 1.75rem;
  cursor: pointer;
  color: #888;
}

.tabs {
  display: flex;
  margin-bottom: 1rem;
}

.tab {
  flex: 1;
  padding: 0.75rem 1rem;
  background-color: #e0e7ff;
  border: none;
  cursor: pointer;
  font-weight: 600;
  color: #141b4d;
  transition: background-color 0.3s ease;
}

.tab:not(:last-child) {
  border-right: 1px solid #c7d2fe;
}

.tab.active {
  background-color: #141b4d;
  color: #ffffff;
}

.tab:hover {
  background-color: #c7d2fe;
}

.tab-content {
  flex: 1;
}

.summary {
  display: flex;
  justify-content: space-between;
  margin-bottom: 1rem;
}

.summary-item {
  display: flex;
  align-items: center;
}

.summary-label {
  font-weight: 600;
  margin-right: 0.5rem;
  color: #2e3a59;
}

.summary-value {
  font-weight: 500;
  color: #141b4d;
}

.teams {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.team {
  background-color: #ffffff;
  border: 1px solid #dfe3e8;
  border-radius: 8px;
  padding: 1rem;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
  cursor: pointer; /* Indicate that the card is clickable */
}

.team-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 0.75rem;
}

.team-header h3 {
  margin: 0;
  font-size: 1rem;
  color: #2e3a59;
}

.badge-container {
  display: flex;
  gap: 0.5rem;
}

.badge {
  display: inline-block;
  padding: 0.25rem 0.75rem;
  border-radius: 12px;
  font-size: 0.85em;
  color: #ffffff;
}

.badge-office {
  background-color: #6BCB77; /* Green */
}

.badge-wfh {
  background-color: #FFD93D; /* Yellow */
  color: #000000; /* Black text for better visibility */
}

.staff-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.staff-card {
  display: flex;
  align-items: center;
  border: 1px solid #dfe3e8;
  border-radius: 6px;
  padding: 0.5rem 0.75rem;
  background-color: #f9fafb;
}

.status-indicator {
  display: inline-block;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  margin-right: 0.75rem;
}

.status-indicator.green {
  background-color: #6BCB77;
}

.status-indicator.yellow {
  background-color: #FFD93D;
}

.staff-name {
  font-weight: 500;
  color: #2e3a59;
  font-size: 0.8rem;
}

.no-selection {
  text-align: center;
  color: #888;
  margin-top: 2rem;
  font-size: 1.1rem;
}

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

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

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

.calendar-event {
  /* Additional styling if needed */
}

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

/* Adjust the calendar height */
.fc .fc-view-harness {
  min-height: 0;
}

/* Hide the scrollbar but allow scrolling */
.calendar-container::-webkit-scrollbar {
  width: 0;
  height: 0;
}

.right-container::-webkit-scrollbar {
  width: 8px;
}

.right-container::-webkit-scrollbar-thumb {
  background-color: rgba(0, 0, 0, 0.2);
  border-radius: 4px;
}

.right-container::-webkit-scrollbar-track {
  background-color: transparent;
}

/* Media query for responsive layout */
@media (max-width: 768px) {
  .calendar-container {
    width: 100%;
  }

  .right-container {
    position: fixed; /* Change to fixed for overlay */
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    background-color: rgba(255, 255, 255, 0.95); /* Semi-transparent background */
    transform: translateX(100%);
    transition: transform 0.3s ease-in-out;
    z-index: 1000;
    overflow-y: auto;
    padding: 1.5rem;
  }

  .right-container.show {
    transform: translateX(0);
  }

  /* Ensure text within badges remains black on yellow backgrounds */
  .badge-wfh, .badge-wfh-calendar {
    color: #000000 !important;
  }
}

@media (max-width: 768px) {
  .main-section {
    flex-direction: column;
  }

  .right-container {
    width: 100%;
    min-width: unset;
    height: auto;
    position: fixed; /* Ensure it overlays */
    right: 0;
    top: 0;
    box-shadow: none;
    background-color: #ffffff;
  }

  .calendar-container {
    width: 100%;
    height: 100%;
  }

  .details-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
  }

  .tabs {
    flex-direction: row;
    justify-content: space-between;
  }
}

/* New Styles for Conditional Text Colors */
.yellow-event {
  color: #000000 !important;
}

.default-event {
  color: #ffffff !important;
}

.yellow-event .badge,
.default-event .badge {
  color: inherit !important;
}

/* Loading Overlay Styles */
.loading-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(255, 255, 255, 0.8);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 10;
}

.spinner {
  border: 8px solid #f3f3f3; /* Light grey */
  border-top: 8px solid #3498db; /* Blue */
  border-radius: 50%;
  width: 60px;
  height: 60px;
  animation: spin 2s linear infinite;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

/* Indicate that team cards are clickable */
.team.card {
  transition: background-color 0.2s ease;
}

.team.card:hover {
  background-color: #f0f4ff;
}
</style>

File: frontend\src\views\RequestView.vue
--------------------------------------------------------------------------------
<template>
  <div class="dashboard">
    <Navbar />

    <div class="row w-100 ms-0">
      <div class="col">
        <div class="card">
          <div class="header">
            <h2 class="h4 mb-0 fw-bold">Pending WFH Requests</h2>
          </div>
          <div v-if="isLoaded" class="card-body shadow">
            <div v-if="pendingRequests.length > 0" class="table">
              <table class="table table-hover">
                <thead class="thead-light">
                  <tr>
                    <th>Date Requested</th>
                    <th>Name</th>
                    <th>Start Date</th>
                    <th>End Date</th>
                    <th>Reason</th>
                    <th>Request Type</th>
                    <th>Approve/Reject</th>
                  </tr>
                </thead>
                <tbody>
                  <tr v-for="request in pendingRequests" :key="request.request_id">
                    <td>{{ formatDate(request.request_date) }}</td>
                    <td>{{ staffNames[request.staff_id] || 'Loading...' }}</td>
                    <td>{{ formatDate(request.start_date) }}</td>
                    <td>{{ request.end_date ? formatDate(request.end_date) : '-' }}</td>
                    <td>{{ request.reason_for_applying }}</td>
                    <td>
                      <span v-if="request.is_recurring" class="badge bg-info ms-1">Recurring</span>
                      <span v-else="request.is_recurring" class="badge bg-primary ms-1"
                        >Ad-hoc</span
                      >
                      <span v-if="request.duration === 'FULL_DAY'" class="badge bg-success ms-1">FULL DAY</span>
          <span v-else-if="request.duration === 'HALF_DAY_AM'" class="badge bg-warning text-dark ms-1">AM</span>
          <span v-else-if="request.duration === 'HALF_DAY_PM'" class="badge bg-warning text-dark ms-1">PM</span>
                    </td>
                    <td>
                      <span><button class="btn-approve" @click="approveRequest(request)" >Approve</button></span>
                      <span><button class="btn-rej" @click = "openRejectForm(request)">Reject</button></span>
                    </td>
                  </tr>
                </tbody>
              </table>
              <div v-if="showForm" class="form-overlay">
                <div class="form-container">
                  <div class="form-header">
                    <h3 class="form-title">Reject WFH Request</h3>
                  </div>
                  <div class="table-wrapper">
                    <table class="table table-bordered" style="overflow:scroll;">
                    <thead class="thead-light">
                      <tr>
                        <th>Date Requested</th>
                        <th>Name</th>
                        <th>Start Date</th>
                        <th>End Date</th>
                        <th>Reason</th>
                      </tr>
                    </thead>
                    <tbody>
                      <tr>
                        <td>{{ formatDate(selectedRequest.request_date) }}</td>
                        <td>{{ staffNames[selectedRequest.staff_id] || 'Loading...' }}</td>
                        <td>{{ formatDate(selectedRequest.start_date) }}</td>
                        <td>{{ selectedRequest.end_date ? formatDate(selectedRequest.end_date) : '-' }}</td>
                        <td>{{ selectedRequest.reason_for_applying }}</td>
                      </tr>
                    </tbody>
                  </table>

                  </div>

                  <div class="form-group text-left px-5">
                    <label for="rej_reason" class="form-label">Reason for rejection:</label>
                    <textarea id="rej_reason" v-model="rej_reason" required class="form-control w-100" rows="3"></textarea>
                  </div>
                  <p v-if="errorMessage" class="error-message">{{ errorMessage }}</p>
                  <div class="form-actions">
                    <button type="button" @click="rejectRequest" class="btn btn-danger">Reject</button>
                    <button type="button" @click="closeForm" class="btn btn-secondary">Cancel</button>
                  </div>
                </div>
              </div>
            </div>
            <p v-else class="text-muted">No pending requests.</p>
          </div>
        </div>
      </div>
    </div>
    <!-- Approve Confirmation Modal -->
<div v-if="showApproveConfirmation" class="form-overlay">
    <div class="form-container" style="width: 400px;">
        <div class="form-header">
            <h3 class="form-title">Confirm Approval</h3>
        </div>
        <div class="p-4">
            <p class="mb-4">Are you sure you want to approve this WFH request?</p>
            <p class="text-muted small mb-4">This action cannot be undone.</p>
            <div class="form-actions">
                <button type="button" @click="confirmApprove" class="btn btn-success">
                    Yes, Approve Request
                </button>
                <button type="button" @click="closeApproveConfirmation" class="btn btn-secondary">
                    No, Cancel
                </button>
            </div>
        </div>
    </div>
</div>
  </div>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'
import { format, isValid } from 'date-fns'
import Navbar from '@/components/Navbar.vue'

const user = ref({})
const userData = JSON.parse(localStorage.getItem('user'))
const pendingRequests = ref([])
const isLoaded = ref(false)
const showApproveConfirmation = ref(false);
const requestToApprove = ref(null);

const fetchPendingRequests = async () => {
  try {
    const userData = JSON.parse(localStorage.getItem('user'));
    const response = await axios.get(
      `${import.meta.env.VITE_API_URL}/api/pending-requests/${userData.staff_id}`
    )
    pendingRequests.value = response.data

    // Fetch names for all staff IDs
    for (const request of pendingRequests.value) {
      if (!staffNames.value[request.staff_id]) {
        await getNameById(request.staff_id)
      }
    }
  } catch (error) {
    console.error('Error fetching pending requests:', error)
    alert('Error fetching pending requests')
  }finally{
    isLoaded.value = true
  }
}

const formatDate = (dateString) => {
  return format(new Date(dateString), 'MMM d, yyyy')
}

const staffNames = ref({})
const getNameById = async (staffId) => {
  if (staffNames.value[staffId]) {
    return staffNames.value[staffId]
  }
  try {
    const response = await axios.get(`${import.meta.env.VITE_API_URL}/api/staff/${staffId}`)
    const name = `${response.data.staff_fname} ${response.data.staff_lname}`
    staffNames.value[staffId] = name

    return name
  } catch (error) {
    console.error('Error fetching staff name:', error)
    alert('Error fetching staff name')
    return 'Unknown'
  }
}
onMounted(() => {
  const storedUser = localStorage.getItem('user')
  if (storedUser) {
    user.value = JSON.parse(storedUser)
    fetchPendingRequests()
  }
})

const showForm = ref(false);
const rej_reason = ref('');
const selectedRequest = ref(null);
const errorMessage = ref('');

const openRejectForm = (request) => {
  // Log the request to inspect the data passed
  console.log('Request data:', request);

  // Check if the request object and date fields are defined
  if (!request || !request.request_date || !request.start_date) {
    console.error('Invalid request data:', request);

    return;
  }

  // Assign the selected request and open the form
  selectedRequest.value = request;
  showForm.value = true;
};

const closeForm = () => {
  showForm.value = false;
  rej_reason.value = '';
  errorMessage.value = '';
  };

  const approveRequest = (request) => {
    requestToApprove.value = request;
    showApproveConfirmation.value = true;
};

// Handle the actual approval
const confirmApprove = async () => {
    try {
        const response = await axios.patch(`${import.meta.env.VITE_API_URL}/api/update-request`, {
            request_id: requestToApprove.value.request_id,
            request_status: 'APPROVED',
            reason: ''
        });
        console.log('Approval response:', response.data);
        showApproveConfirmation.value = false;
        await fetchPendingRequests();
    } catch (error) {
        console.error('Error approving request:', error);
        if (error.response && error.response.data && error.response.data.message) {
            alert(`Error approving request: ${error.response.data.message}`);
        } else {
            alert('Error approving request');
        }
    }
};

// Close the confirmation modal
const closeApproveConfirmation = () => {
    showApproveConfirmation.value = false;
    requestToApprove.value = null;
};


const rejectRequest = async () => {
  if (!rej_reason.value.trim()) {
    errorMessage.value = 'Please provide a reason for rejection.';
    return;
  }

  errorMessage.value = '';

  try {
    const response = await axios.patch(`${import.meta.env.VITE_API_URL}/api/update-request`, {
      request_id: selectedRequest.value.request_id,
      request_status: 'REJECTED',
      reason: rej_reason.value
    });
    console.log('Rejection response:', response.data);
    closeForm();
    // Refresh the pending requests list
    await fetchPendingRequests();
  } catch (error) {
    console.error('Error rejecting request:', error);
    alert('Error rejecting request');
  }
};


</script>

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

.dashboard-content {
  flex: 1;
  padding: 2rem 0;
}

.header {
  color: #141b4d;
  padding-top: 1rem;
  padding-bottom: 1rem;

}
.card {
  border: none;
  transition: box-shadow 0.3s ease-in-out;
}

.table {
  overflow-x: scroll;
}

.table th {
  font-weight: 600;
}

.table-wrapper {
  max-width: 100%;
  overflow-x: auto;
}

.badge {
  font-weight: 500;
}

.badge.bg-success {
  background-color: #28a745 !important;
}
.badge.bg-warning {
  background-color: #ffc107 !important;
}
.text-dark {
  color: #343a40 !important;
}

button {
  width: 100%;
  padding: 0.5rem;
  margin-bottom: 0.5rem;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  font-size: 1rem;
  font-weight: 600;
  background-color: #1e2a6d
}

.btn-approve {
  background-color: green;
}

.btn-rej {
  background-color: red;
}

button:hover {
  color: black;
  /* font-weight: bold; */
}

.form-container {
  width: 70%;
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background-color: white;
  padding: 20px;
  border-radius: 8px;
  z-index: 100;
  box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  text-align: center;
  padding: 0;
}

.form-header {
  width: 100%;
  background-color: #141b4d;
  padding: 1rem;
  border-top-left-radius: 8px;
  border-top-right-radius: 8px;
  margin-bottom: 2rem;
}

.form-button {
  width: 30%;
  margin: 2rem;
}

#rej_reason {
  width: 100%;
  height: 40%;
}
.form-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.5);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1000;
}

.form-title {
  color: white;
  font-weight: bold;
}

.form-group {
  margin-bottom: 1.5rem;
  width: 100%;
}

.form-label {
  font-weight: 600;
  color: #141b4d;
  margin-bottom: 0.5rem;
  display: block;
  text-align: left;
}

.form-control {
  width: 100%;
  padding: 0.5rem;
  border: 1px solid #ced4da;
  border-radius: 4px;
  font-size: 1rem;
}

.error-message {
  color: #dc3545;
}

.form-actions {
  display: flex;
  justify-content: flex-end;
  gap: 1rem;
}

.btn {
  padding: 0.5rem 1rem;
  font-size: 1rem;
  font-weight: 600;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: background-color 0.2s ease-in-out;
}

.btn-danger {
  background-color: #dc3545;
  color: white;
}

.btn-danger:hover {
  background-color: #c82333;
}

.btn-secondary {
  background-color: #6c757d;
  color: white;
}

.btn-secondary:hover {
  background-color: #5a6268;
}

.btn-success {
    background-color: #28a745;
    color: white;
}

.btn-success:hover {
    background-color: #218838;
}

.form-container.small {
    width: 400px;
}

.form-actions .btn {
    width: auto;
    margin-bottom: 0;
}

.text-muted {
    color: #6c757d;
}

.small {
    font-size: 0.875rem;
}
</style>


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 v-if="isLoading" class="loading-overlay">
            <div class="spinner"></div>
          </div>
        </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([]); // Reactive array to hold events
const isLoading = ref(false);

// User data from local storage
onMounted(() => {
  const storedUser = localStorage.getItem('user');
  if (storedUser) {
    user.value = JSON.parse(storedUser);
  }
  initiateChunkedSummaryLoading(); // Fetch events on mount
});

// Computed properties for valid date range
const computeMinDate = computed(() => {
  const date = new Date();
  date.setMonth(date.getMonth() - 2);
  return date;
});

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

// Function to fetch events from the backend
async function fetchEvents(startDate, endDate) {
  try {
    const startDateStr = startDate.toISOString().split('T')[0];
    const endDateStr = endDate.toISOString().split('T')[0];
    const response = await axios.get(`${import.meta.env.VITE_API_URL}/api/personal-schedule/${user.value.staff_id}`, {
      params: {
        start_date: startDateStr,
        end_date: endDateStr
      }
    });
    const fetchedDates = response.data.dates;
    console.log(response.data);

    // Clear existing events before pushing new ones
    const newEvents = [];

    fetchedDates.forEach(dateObj => {
      const eventDate = dateObj.date;
      const wfhStatus = dateObj.schedule;

      if (wfhStatus === 'AM') {
        newEvents.push({
          title: 'WFH (AM)',
          start: `${eventDate}T09:00:00`,
          end: `${eventDate}T13:00:00`,
          backgroundColor: '#FFD93D',
        });
      } else if (wfhStatus === 'PM') {
        newEvents.push({
          title: 'WFH (PM)',
          start: `${eventDate}T14:00:00`,
          end: `${eventDate}T18:00:00`,
          backgroundColor: '#FFD93D',
        });
      } else if (wfhStatus === 'FullDay') {
        newEvents.push({
          title: 'WFH (Full Day)',
          start: `${eventDate}T09:00:00`,
          end: `${eventDate}T18:00:00`,
          backgroundColor: '#FFD93D',
          color: 'black'
        });
      } else if (wfhStatus === 'FullDayPending') {
        newEvents.push({
          title: 'Pending WFH (Full Day)',
          start: `${eventDate}T09:00:00`,
          end: `${eventDate}T18:00:00`,
          backgroundColor: '#141b4d'
        });
      } else if (wfhStatus === 'AMPending') {
        newEvents.push({
          title: 'Pending WFH (AM)',
          start: `${eventDate}T09:00:00`,
          end: `${eventDate}T13:00:00`,
          backgroundColor: '#141b4d'
        });
      } else if (wfhStatus === 'PMPending') {
        newEvents.push({
          title: 'Pending WFH (PM)',
          start: `${eventDate}T14:00:00`,
          end: `${eventDate}T18:00:00`,
          backgroundColor: '#141b4d'
        });
      } 
    });

    // Update the reactive events array
    events.value.push(...newEvents); 

  } catch (error) {
    console.error('Error fetching events:', error);
  }
}

// Calendar options
const calendarOptions = computed(() => ({
  plugins: [dayGridPlugin, timeGridPlugin, interactionPlugin],
  initialView: 'dayGridMonth',
  headerToolbar: {
    left: 'prev,next today',
    center: 'title',
    right: 'dayGridMonth,timeGridWeek,timeGridDay',
  },
  dateClick: handleDateClick,
  selectable: true,
  validRange: {
    start: computeMinDate.value,
    end: computeMaxDate.value,
  },
  weekends: false,
  events: events.value,  // Make sure this is reactive
  eventContent: function(arg) {
    const container = document.createElement('div');

    container.style.borderRadius = '4px';
    container.style.margin = '1px 0';
    container.style.padding = '2px 4px';
    container.style.fontSize = '0.8em';
    container.style.width = '100%';
    container.style.height = '100%';
    container.style.backgroundColor = arg.event.backgroundColor || '#3788d8';

    const title = document.createElement('span');
    title.textContent = arg.event.title;
    title.style.fontWeight = 'bold';
    title.style.fontSize = '12px';
    if (arg.event.backgroundColor === '#FFD93D') {
      title.style.color = 'black';
    } else {
      title.style.color = 'white';
    }


    container.appendChild(title);

    if (arg.event.extendedProps.timeOfDay) {
      const timeOfDay = document.createElement('span');
      timeOfDay.textContent = arg.event.extendedProps.timeOfDay;
      timeOfDay.style.fontSize = '10px';
      timeOfDay.style.color = 'lightgray';
      container.appendChild(timeOfDay);
    }

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

// Handle date click event
async function handleDateClick(info) {
  const clickedDate = new Date(info.dateStr);

  if (clickedDate < computeMinDate.value) {
    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 {
    console.log('Date clicked:', info.dateStr);
    selectedDate.value = info.dateStr;
  }
}

async function initiateChunkedSummaryLoading() {
  isLoading.value = true;
  const today = new Date();

  // Define the date ranges
  const ranges = [];

  // Current month
  const currentMonthStart = new Date(today.getFullYear(), today.getMonth(), 1);
  const currentMonthEnd = new Date(today.getFullYear(), today.getMonth() + 1, 0);
  ranges.push({ start: currentMonthStart, end: currentMonthEnd });

  // Past 2 months
  for (let i = 1; i <= 2; i++) {
    const start = new Date(today.getFullYear(), today.getMonth() - i, 1);
    const end = new Date(today.getFullYear(), today.getMonth() - i + 1, 0);
    ranges.push({ start, end });
  }

  // Next 3 months
  for (let i = 1; i <= 3; i++) {
    const start = new Date(today.getFullYear(), today.getMonth() + i, 1);
    const end = new Date(today.getFullYear(), today.getMonth() + i + 1, 0);
    ranges.push({ start, end });
  }

  // Load the current month events first
  await fetchEvents(ranges[0].start, ranges[0].end);

  isLoading.value = false;
  
  // Load past 2 and next 3 months together
  const promises = [];
  for (let i = 1; i < ranges.length; i++) {
    promises.push(fetchEvents(ranges[i].start, ranges[i].end));
  }

  // Wait for all past and future month events to load
  await Promise.all(promises);

}

</script>

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

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

.main-section {
  display: flex;
  justify-content: center;
  height: 100%;
  position: relative; /* Add this to position the overlay correctly */
}

.calendar-container {
  width: 85%; 
  padding: 1rem;
  position: relative; /* Add this to ensure the overlay is positioned relative to this container */
}

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

.fc-daygrid-event {
  width: 100% !important;
  height: auto;
  white-space: normal; /* Prevent text overflow */
}

.fc-daygrid-event > div {
  display: flex;
  justify-content: center;
  align-items: center;
  width: 90%;  /* Ensure container spans the full width */
}

.loading-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(255, 255, 255, 0.8);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 10;
  pointer-events: none; /* Allow interactions with elements underneath */
}

.spinner {
  border: 8px solid #f3f3f3; /* Light grey */
  border-top: 8px solid #3498db; /* Blue */
  border-radius: 50%;
  width: 60px;
  height: 60px;
  animation: spin 2s linear infinite;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}
</style>



File: frontend\src\views\StaffRequestView.vue
--------------------------------------------------------------------------------
<template>
    <div class="dashboard">
        <Navbar />

        <div class="row w-100 ms-0">
            <div class="col">
                <div class="card">
                    <div class="header">
                        <h2 class="h4 mb-0 fw-bold">My WFH Requests</h2>
                    </div>
                    <div class="filter-buttons d-flex justify-content-start align-items-center my-4 px-3">
    <div class="toggle-container">
        <div v-for="status in statuses" 
             :key="status" 
             @click="filterStatus = status" 
             :class="['toggle-button', {'active': filterStatus === status}]">
            {{ status }}
        </div>
    </div>
</div>
                    <div v-if="isLoaded" class="card-body shadow">
                        <div v-if="filteredRequests.length > 0" class="table">
                            <table class="table table-hover">
                                <thead class="thead-light">
                                    <tr>
                                        <th>Date Requested</th>
                                        <th>Start Date</th>
                                        <th>End Date</th>
                                        <th>Reason</th>
                                        <th>Request Type</th>
                                        <th>Status</th>
                                        <th>Action</th>
                                    </tr>
                                </thead>
                                <tbody>
                                    <tr v-for="request in filteredRequests" :key="request.request_id">
                                        <td>{{ formatDate(request.request_date) }}</td>
                                        <td>{{ formatDate(request.start_date) }}</td>
                                        <td>{{ request.end_date ? formatDate(request.end_date) : '-' }}</td>
                                        <td>{{ request.reason_for_applying }}</td>
                                        <td>
                                            <span v-if="request.is_recurring" class="badge bg-info ms-1">Recurring</span>
                                            <span v-else class="badge bg-primary ms-1">Ad-hoc</span>
                                            <span v-if="request.duration === 'FULL_DAY'" class="badge bg-success ms-1">FULL DAY</span>
                                            <span v-else-if="request.duration === 'HALF_DAY_AM'" class="badge bg-warning text-dark ms-1">AM</span>
                                            <span v-else-if="request.duration === 'HALF_DAY_PM'" class="badge bg-warning text-dark ms-1">PM</span>
                                        </td>
                                        <td>
                                            <span :class="getRequestStatus(request.status)">{{ request.status }}</span>
                                        </td>
                                        <td>
                                            <button v-if="request.status === 'PENDING'" class="btn btn-danger btn-sm"
                                                @click="cancelRequest(request.request_id)">
                                                Cancel
                                            </button>
                                            <button
                                                v-if="request.status === 'APPROVED'"
                                                :class="{
                                                    'btn btn-warning btn-sm': isWithinWithdrawalPeriod(request.start_date),
                                                    'btn btn-secondary btn-sm': !isWithinWithdrawalPeriod(request.start_date)
                                                }"
                                                @click="openRejectForm(request)"
              
                                                :disabled="!isWithinWithdrawalPeriod(request.start_date)"
                                            >
                                                Withdraw
                                            </button>
                                        </td>
                                    </tr>
                                </tbody>
                            </table>
                            <div v-if="showForm" class="form-overlay">
                                <div class="form-container">
                                    <div class="form-header">
                                        <h3 class="form-title">Withdraw WFH Request</h3>
                                    </div>
                                    <div class="table-wrapper">
                                        <table class="table table-bordered" style="overflow:scroll;">
                                            <thead class="thead-light">
                                                <tr>
                                                    <th>Date Requested</th>
                                                    <th>Start Date</th>
                                                    <th>End Date</th>
                                                    <th>Reason</th>
                                                </tr>
                                            </thead>
                                            <tbody>
                                                <tr>
                                                    <td>{{ formatDate(selectedRequest.request_date) }}</td>
                                                    <td>{{ formatDate(selectedRequest.start_date) }}</td>
                                                    <td>{{ selectedRequest.end_date ? formatDate(selectedRequest.end_date) : '-' }}</td>
                                                    <td>{{ selectedRequest.reason_for_applying }}</td>
                                                </tr>
                                            </tbody>
                                        </table>
                                    </div>
                                    <div class="form-group text-left px-5">
                                        <label for="rej_reason" class="form-label">Reason for withdrawal:</label>
                                        <textarea id="rej_reason" v-model="rej_reason" required class="form-control w-100" rows="3"></textarea>
                                    </div>
                                    <p v-if="errorMessage" class="error-message">{{ errorMessage }}</p>
                                    <div class="form-actions">
                                        <button type="button" @click="withdrawRequest" class="btn btn-danger">Withdraw</button>
                                        <button type="button" @click="closeForm" class="btn btn-secondary">Cancel</button>
                                    </div>
                                </div>
                            </div>
                        </div>
                        <p v-else class="text-muted">
                            No {{ filterStatus === 'All' ? '' : filterStatus }} requests found.
                        </p>
                    </div>
                </div>
            </div>
        </div>

<!-- Cancel Confirmation Modal -->
<div v-if="showCancelConfirmation" class="form-overlay">
    <div class="form-container" style="width: 400px;">
        <div class="form-header">
            <h3 class="form-title">Confirm Cancellation</h3>
        </div>
        <div class="p-4">
            <p class="mb-4">Are you sure you want to cancel this WFH request?</p>
            <p class="text-muted small mb-4">This action cannot be undone.</p>
            <div class="form-actions">
                <button type="button" @click="confirmCancel" class="btn btn-danger">
                    Yes, Cancel Request
                </button>
                <button type="button" @click="closeCancelConfirmation" class="btn btn-secondary">
                    No, Keep Request
                </button>
            </div>
        </div>
    </div>
</div>
    </div>

</template>

<script setup>
import { ref, onMounted, computed } from 'vue'
import axios from 'axios'
import { format, isValid } from 'date-fns'
import Navbar from '@/components/Navbar.vue'

const userData = ref({});
const allRequests = ref([]);
const isLoaded = ref(false);

const filterStatus = ref('All');;
// const statuses = ['All', 'Pending', 'Approved', 'Rejected', 'Expired', 'Cancelled', 'Withdrawn'];
const statuses = ['All', 'Pending', 'Approved', 'Rejected', 'Others'];
const showForm = ref(false);
const rej_reason = ref('');
const showCancelConfirmation = ref(false);
const requestToCancel = ref(null);


// error here
const filteredRequests = computed(() => {
    if (filterStatus.value === 'All') {
        return allRequests.value;
    } else if (filterStatus.value === 'Others') {
        return allRequests.value.filter(request =>
            ['EXPIRED', 'CANCELLED', 'WITHDRAWN'].includes(request.status)
        );
    } else {
        return allRequests.value.filter(request => request.status === filterStatus.value.toUpperCase());
    }
});

const fetchRequests = async () => {
    try {
        const userData = JSON.parse(localStorage.getItem('user'));
        const response = await axios.get(
            `${import.meta.env.VITE_API_URL}/api/staff-requests/${userData.staff_id}`
        );

        // console.log('userData:', userData);
        // console.log('response:', response);
        console.log('all requests:', response.data);
        // console.log(typeof (response.data));
        // console.log('err');

        allRequests.value = response.data.staff_requests;
        // console.log('allRequests:', allRequests.value);

    } catch (error) {
        console.error('Error fetching requests:', error);
        alert('Error fetching requests');
    } finally {
        isLoaded.value = true;
    }
}
// Cancel pending request
const cancelRequest = (request_id) => {
    requestToCancel.value = request_id;
    showCancelConfirmation.value = true;
};

// Handle the actual cancellation
const confirmCancel = async () => {
    try {
        const response = await axios.patch(`${import.meta.env.VITE_API_URL}/api/update-request`, {
            request_id: requestToCancel.value,
            request_status: 'CANCELLED',
            reason: ''
        });
        console.log('Cancellation response:', response.data);
        showCancelConfirmation.value = false;
        await fetchRequests();
    } catch (error) {
        console.error('Error cancelling request:', error);
        alert('Error cancelling request');
    }
};

// Close the confirmation modal
const closeCancelConfirmation = () => {
    showCancelConfirmation.value = false;
    requestToCancel.value = null;
};

const isWithinWithdrawalPeriod = (applicationDate) => {
    const appDate = new Date(applicationDate);
    const today = new Date();

    const twoWeeksBefore = new Date(today);
    twoWeeksBefore.setDate(today.getDate() - 14);

    const twoWeeksAfter = new Date(today);
    twoWeeksAfter.setDate(today.getDate() + 14);

    return appDate >= twoWeeksBefore && appDate <= twoWeeksAfter;
};

const openRejectForm = (request) => {
  // Log the request to inspect the data passed
  console.log('Request data:', request);

  // Check if the request object and date fields are defined
  if (!request || !request.request_date || !request.start_date) {
    console.error('Invalid request data:', request);

    return;
  }

  // Assign the selected request and open the form
  selectedRequest.value = request;
  showForm.value = true;
};

const closeForm = () => {
  showForm.value = false;
  rej_reason.value = '';
  errorMessage.value = '';
  };

const withdrawRequest = async (request_id) => {
    if (!rej_reason.value.trim()) {
        errorMessage.value = 'Please provide a reason for rejection.';
        return;
    }

    errorMessage.value = '';
    try {
        const response = await axios.patch(`${import.meta.env.VITE_API_URL}/api/update-request`, {
            request_id: selectedRequest.value.request_id,
            request_status: 'WITHDRAWN',
            reason: rej_reason.value
        });

        console.log('Withdrawal response:', response.data);
        closeForm();
        await fetchRequests();
    } catch (error) {
        console.error('Error withdrawing request:', error);
        alert('Error withdrawing request');
    }
};




const formatDate = (dateString) => {
    return format(new Date(dateString), 'MMM d, yyyy');
}

const getRequestStatus = (requestStatus) => {
    switch (requestStatus) {
        default: return 'badge bg-secondary';
        case 'PENDING': return 'badge bg-warning';
        case 'APPROVED': return 'badge bg-success';
        case 'REJECTED': return 'badge bg-danger';
        case 'EXPIRED':
        case 'CANCELLED':
        case 'WITHDRAWN': return 'badge bg-secondary'
    }
}

const getStatusButtonClass = (requestStatus) => {
    switch (requestStatus) {
        case 'Pending':
            return filterStatus.value === requestStatus ? 'btn-warning' : 'btn-outline-warning';
        case 'Approved':
            return filterStatus.value === requestStatus ? 'btn-success' : 'btn-outline-success';
        case 'Rejected':
            return filterStatus.value === requestStatus ? 'btn-danger' : 'btn-outline-danger';
        case 'Others':
            return filterStatus.value === requestStatus ? 'btn-secondary' : 'btn-outline-secondary';
        default:
            return 'btn-outline-secondary';
    }
};

onMounted(() => {
    const storedUser = localStorage.getItem('user');
    if (storedUser) {
        userData.value = JSON.parse(storedUser);
        fetchRequests();
    }
})

const selectedRequest = ref(null);
const errorMessage = ref('');

</script>

<style scoped>
.active-filter {
    background-color: inherit;
    color: inherit;
    border-color: black;
}

.dashboard-container {
    display: flex;
    flex-direction: column;
    min-height: 100vh;
}

.dashboard-content {
    flex: 1;
    padding: 2rem 0;
}

.header {
    color: #141b4d;
    padding-top: 1rem;
}

.card {
    border: none;
    transition: box-shadow 0.3s ease-in-out;
}

.table {
    overflow-x: scroll;
}

.table th {
    font-weight: 600;
}

.table-wrapper {
    max-width: 100%;
    overflow-x: auto;
}

.badge {
    font-weight: 500;
}

.badge.bg-success {
    background-color: #28a745 !important;
}

.badge.bg-warning {
    background-color: #ffc107 !important;
}

.text-dark {
    color: #343a40 !important;
}

button {
    width: 100%;
    padding: 0.5rem;
    margin-bottom: 0.5rem;
    color: white;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    font-size: 1rem;
    font-weight: 600;
    background-color: #1e2a6d
}

.btn-approve {
    background-color: green;
}

.btn-rej {
    background-color: red;
}

button:hover {
    color: black;
    /* font-weight: bold; */
}

.form-container {
    width: 70%;
    position: fixed;
    top: 50%;
    left: 50%;
    transform: translate(-50%, -50%);
    background-color: white;
    padding: 20px;
    border-radius: 8px;
    z-index: 100;
    box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1);
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    text-align: center;
    padding: 0;
}

.form-header {
    width: 100%;
    background-color: #141b4d;
    padding: 1rem;
    border-top-left-radius: 8px;
    border-top-right-radius: 8px;
    margin-bottom: 2rem;
}

.form-button {
    width: 30%;
    margin: 2rem;
}

#rej_reason {
    width: 100%;
    height: 40%;
}

.form-overlay {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background-color: rgba(0, 0, 0, 0.5);
    display: flex;
    justify-content: center;
    align-items: center;
    z-index: 1000;
}

.form-title {
    color: white;
    font-weight: bold;
}

.form-group {
    margin-bottom: 1.5rem;
    width: 100%;
}

.form-label {
    font-weight: 600;
    color: #141b4d;
    margin-bottom: 0.5rem;
    display: block;
    text-align: left;
}

.form-control {
    width: 100%;
    padding: 0.5rem;
    border: 1px solid #ced4da;
    border-radius: 4px;
    font-size: 1rem;
}

.error-message {
    color: #dc3545;
}

.form-actions {
    display: flex;
    justify-content: flex-end;
    gap: 1rem;
}

.btn {
    padding: 0.5rem 1rem;
    font-size: 1rem;
    font-weight: 600;
    border: none;
    border-radius: 4px;
    cursor: pointer;
    transition: background-color 0.2s ease-in-out;
}

.btn-warning {
    background-color: #ffc107;
}

.btn-success {
    background-color: #28a745;
}

.btn-danger {
    background-color: #dc3545;
    color: white;
}

.btn-danger:hover {
    background-color: #c82333;
}

.btn-secondary {
    background-color: #6c757d;
    color: white;
}

.btn-secondary:hover {
    background-color: #5a6268;
}
.form-container.small {
    width: 400px;
}

.form-actions .btn {
    width: auto;
    margin-bottom: 0;
}

.text-muted {
    color: #6c757d;
}

.small {
    font-size: 0.875rem;
}


.filter-buttons {
    width: 100%;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch; /* Smooth scrolling on iOS */
    scrollbar-width: none; /* Hide scrollbar for Firefox */
    -ms-overflow-style: none; /* Hide scrollbar for IE/Edge */
}

.filter-buttons::-webkit-scrollbar {
    display: none; /* Hide scrollbar for Chrome/Safari */
}

.toggle-container {
    display: flex;
    background-color: #f0f2f5;
    padding: 0.3rem;
    border-radius: 12px;
    gap: 0.3rem;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
    min-width: fit-content; /* Ensures buttons don't shrink too much */
}

.toggle-button {
    padding: 0.5rem 1.2rem;
    border-radius: 10px;
    cursor: pointer;
    font-size: 0.9rem;
    font-weight: 500;
    transition: all 0.3s ease;
    color: #666;
    user-select: none;
    white-space: nowrap; /* Prevents text wrapping */
    min-width: max-content; /* Ensures text doesn't get cut off */
}

/* Media queries for different screen sizes */
@media screen and (max-width: 768px) {
    .toggle-button {
        padding: 0.4rem 1rem;
        font-size: 0.85rem;
    }
}

@media screen and (max-width: 480px) {
    .filter-buttons {
        padding: 0 0.5rem !important;
    }
    
    .toggle-container {
        padding: 0.6rem;
        gap: 0.4rem;
    }
    
    .toggle-button {
        padding: 0.35rem 0.8rem;
        font-size: 0.8rem;
    }
}

/* Rest of your existing toggle-button styles remain the same */
.toggle-button:hover {
    background-color: rgba(255, 255, 255, 0.8);
    color: #333;
}

.toggle-button.active {
    background-color: white;
    color: #141b4d;
    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.toggle-button.active:has(:contains("Pending")) {
    color: #ffc107;
}

.toggle-button.active:has(:contains("Approved")) {
    color: #28a745;
}

.toggle-button.active:has(:contains("Rejected")) {
    color: #dc3545;
}

.toggle-button.active:has(:contains("Others")) {
    color: #6c757d;
}
</style>


File: frontend\src\views\StaffTeamScheduleView.vue
--------------------------------------------------------------------------------
<template>
  <div id="app">
    <Navbar />
    <div class="dashboard-container">
      <div class="main-section">
        <div class="calendar-container">
          <FullCalendar 
            :options="calendarOptions"
          />
          <div v-if="isLoading" class="loading-overlay">
            <div class="spinner"></div>
          </div>
        </div>
        <div class="right-container" :class="{'show': isRightContainerVisible }">
          <div v-if="selectedDateDetails" class="details-container">
            <div class="details-header">
              <h2>Details for {{ selectedDate }}</h2>
              <!-- Close button is only rendered for small screens -->
              <button 
                v-if="isSmallScreen" 
                class="close-button" 
                @click="isRightContainerVisible = false"
              >
                ×
              </button>
            </div>
            <div class="tabs">
              <button 
                :class="['tab', activeTab === 'AM' ? 'active' : '']" 
                @click="activeTab = 'AM'"
              >
                AM
              </button>
              <button 
                :class="['tab', activeTab === 'PM' ? 'active' : '']" 
                @click="activeTab = 'PM'"
              >
                PM
              </button>
            </div>
            <div class="tab-content">
              <div v-if="activeTab === 'AM'">
                <div class="summary">
                  <div class="summary-item">
                    <span class="summary-label">In Office:</span>
                    <span class="summary-value">{{ selectedDateDetails.AM.inOffice }} / {{ selectedDateDetails.AM.total }}</span>
                  </div>
                  <div class="summary-item">
                    <span class="summary-label">WFH:</span>
                    <span class="summary-value">{{ selectedDateDetails.AM.wfh }} / {{ selectedDateDetails.AM.total }}</span>
                  </div>
                </div>
                <div class="teams">
                  <div 
                    class="team card" 
                    v-for="team in selectedDateDetails.AM.teams" 
                    :key="team.teamName"
                    @click="toggleTeam('AM', team.teamName)"
                  >
                    <div class="team-header">
                      <h3>{{ team.teamName }}</h3>
                      <div class="badge-container">
                        <span class="badge badge-office">
                          In Office: {{ team.inOffice }}/{{ team.total }}
                        </span>
                        <span class="badge badge-wfh">
                          WFH: {{ team.wfh }}/{{ team.total }}
                        </span>
                      </div>
                    </div>
                    <div class="staff-list" v-if="expandedTeams['AM-' + team.teamName]">
                      <div class="staff-card" v-for="staff in team.staff" :key="staff.staff_id">
                        <span :class="['status-indicator', staff.status === 'OFFICE' ? 'green' : 'yellow']"></span>
                        <span class="staff-name">{{ staff.name }}</span>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
              <div v-else-if="activeTab === 'PM'">
                <div class="summary">
                  <div class="summary-item">
                    <span class="summary-label">In Office:</span>
                    <span class="summary-value">{{ selectedDateDetails.PM.inOffice }} / {{ selectedDateDetails.PM.total }}</span>
                  </div>
                  <div class="summary-item">
                    <span class="summary-label">WFH:</span>
                    <span class="summary-value">{{ selectedDateDetails.PM.wfh }} / {{ selectedDateDetails.PM.total }}</span>
                  </div>
                </div>
                <div class="teams">
                  <div 
                    class="team card" 
                    v-for="team in selectedDateDetails.PM.teams" 
                    :key="team.teamName"
                    @click="toggleTeam('PM', team.teamName)"
                  >
                    <div class="team-header">
                      <h3>{{ team.teamName }}</h3>
                      <div class="badge-container">
                        <span class="badge badge-office">
                          In Office: {{ team.inOffice }}/{{ team.total }}
                        </span>
                        <span class="badge badge-wfh">
                          WFH: {{ team.wfh }}/{{ team.total }}
                        </span>
                      </div>
                    </div>
                    <div class="staff-list" v-if="expandedTeams['PM-' + team.teamName]">
                      <div class="staff-card" v-for="staff in team.staff" :key="staff.staff_id">
                        <span :class="['status-indicator', staff.status === 'OFFICE' ? 'green' : 'yellow']"></span>
                        <span class="staff-name">{{ staff.name }}</span>
                      </div>
                    </div>
                  </div>
                </div>
              </div>
            </div>
          </div>
          <div v-else class="no-selection">
            <p>Select a date on the calendar to view details.</p>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, computed, onUnmounted } 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 selectedDateDetails = ref(null);
const isRightContainerVisible = ref(false);
const isSmallScreen = ref(window.innerWidth < 768);
const isLoading = ref(false);
const activeTab = ref('AM');
const expandedTeams = ref({});

const handleResize = () => {
  isSmallScreen.value = window.innerWidth < 768;
};

onMounted(() => {
  const storedUser = localStorage.getItem('user');
  if (storedUser) {
    user.value = JSON.parse(storedUser);
    if (user.value.role === 2) {
      initiateChunkedSummaryLoading();
    }
  }
  window.addEventListener('resize', handleResize);
});

onUnmounted(() => {
  window.removeEventListener('resize', handleResize);
});

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

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

function getColorForPercentage(percentage) {
  percentage = parseFloat(percentage);
  if (percentage != 100) {
    return '#FFD93D'; // Yellow
  }   else {
    return '#6BCB77'; // Green
  }
}

async function fetchStaffScheduleSummary(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/staff-schedule-summary/${user.value.reporting_manager}`, {
      params: {
        start_date: startDateStr,
        end_date: endDateStr,
        staff_id: user.value.staff_id
      }
    });

    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) In Office: ${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) In Office: ${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 = [...events.value, ...newEvents];
  } catch (error) {
    console.error('Error fetching staff schedule summary:', error);
  }
}

function getDateStr(dateObj) {
  const year = dateObj.getFullYear();
  const month = ('0' + (dateObj.getMonth() + 1)).slice(-2);
  const day = ('0' + dateObj.getDate()).slice(-2);
  return `${year}-${month}-${day}`;
}

const calendarOptions = computed(() => {
  let smallScreen = isSmallScreen.value;

  return {
    plugins: [dayGridPlugin, timeGridPlugin, interactionPlugin],
    initialView: 'dayGridMonth',
    headerToolbar: {
      left: 'prev,next today',
      center: 'title',
      right: 'dayGridMonth,timeGridWeek,timeGridDay',
    },
    dateClick: handleDateClick,
    eventClick: handleEventClick,
    datesSet: handleDatesSet,
    selectable: true,
    validRange: {
      start: computeMinDate.value,
      end: computeMaxDate.value,
    },
    weekends: false,
    events: events.value,
    allDaySlot: false,
    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.flexDirection = 'column';
        container.style.justifyContent = 'center';
        container.style.alignItems = 'flex-start';
        container.style.backgroundColor = arg.event.backgroundColor;

        if (arg.event.backgroundColor === '#FFD93D') {
          container.classList.add('yellow-event');
        } else {
          container.classList.add('default-event');
        }

        container.style.borderRadius = '4px';
        container.style.margin = '1px 0';
        container.style.padding = '2px 4px';
        container.style.fontSize = '0.8em';
        container.style.width = '100%';
        container.style.height = '100%';

        const labelContainer = document.createElement('div');
        labelContainer.style.display = 'flex';
        labelContainer.style.alignItems = 'center';
        labelContainer.style.width = '100%';
        labelContainer.style.justifyContent = 'space-between';

        const label = document.createElement('span');
        label.textContent = `${timeOfDay}`;

        const badgesWrapper = document.createElement('div');
        badgesWrapper.style.display = 'flex';
        badgesWrapper.style.gap = '4px';

        const officeBadge = document.createElement('span');
        officeBadge.classList.add('badge', 'badge-office-calendar');
        officeBadge.textContent = `In Office: ${officeCount}`;

        badgesWrapper.appendChild(officeBadge);

        if (!smallScreen) {
          const wfhBadge = document.createElement('span');
          wfhBadge.classList.add('badge', 'badge-wfh-calendar');
          wfhBadge.textContent = `WFH: ${totalStaff - officeCount}`;
          badgesWrapper.appendChild(wfhBadge);
        }

        labelContainer.appendChild(label);
        labelContainer.appendChild(badgesWrapper);

        container.appendChild(labelContainer);

        return { domNodes: [container] };
      }
      return null;
    },
    eventDidMount: function(info) {
      const bgColor = info.event.backgroundColor.toUpperCase();
      const yellowHex = '#FFD93D';

      if (bgColor === yellowHex) {
        info.el.style.color = '#000000';

        const elements = info.el.querySelectorAll('*');
        elements.forEach(el => {
          el.style.color = 'inherit';
        });
      } else {
        info.el.style.color = '#ffffff';

        const elements = info.el.querySelectorAll('*');
        elements.forEach(el => {
          el.style.color = 'inherit';
        });
      }
    },
    eventClassNames: 'calendar-event',
    slotMinTime: '09:00:00',
    slotMaxTime: '19:00:00',
    businessHours: {
      startTime: '09:00',
      endTime: '19:00',
      daysOfWeek: [1, 2, 3, 4, 5],
    },
  };
});

async function handleDateClick(info) {
  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;
      activeTab.value = 'AM';
      if (user.value.role === 2) {
        await displayDateDetails(info.dateStr);
      }
    }
  }
}

async function handleEventClick(info) {
  if (user.value.role === 2) {
    const dateStr = getDateStr(info.event.start);
    const viewType = info.view.type;
    if (viewType === 'timeGridWeek' || viewType === 'timeGridDay') {
      selectedDate.value = dateStr;
      activeTab.value = 'AM';
      await displayDateDetails(dateStr);
    }
  }
}

async function handleDatesSet(info) {
  if (info.view.type === 'timeGridDay') {
    const dateStr = getDateStr(info.start);
    selectedDate.value = dateStr;
    activeTab.value = 'AM';
    if (user.value.role === 2) {
      const detailData = await fetchStaffScheduleDetail(dateStr);
      if (detailData) {
        let groupedData = {
          AM: {
            inOffice: 0,
            wfh: 0,
            total: detailData.staff.length - 1,
            teams: {}
          },
          PM: {
            inOffice: 0,
            wfh: 0,
            total: detailData.staff.length - 1,
            teams: {}
          }
        };

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

          // AM
          if (!groupedData.AM.teams[position]) {
            groupedData.AM.teams[position] = { teamName: position, inOffice: 0, wfh: 0, total: 0, staff: [] };
          }
          if (staff.staff_id != user.value.staff_id){
            groupedData.AM.teams[position].total += 1;
            if (staff.status_am === 'OFFICE') {
              groupedData.AM.teams[position].inOffice += 1;
              groupedData.AM.inOffice += 1;
            } else {
              groupedData.AM.teams[position].wfh += 1;
              groupedData.AM.wfh += 1;
            }
            groupedData.AM.teams[position].staff.push({ staff_id: staff.staff_id, name: staff.name, status: staff.status_am });
          }
          // PM
          if (!groupedData.PM.teams[position]) {
            groupedData.PM.teams[position] = { teamName: position, inOffice: 0, wfh: 0, total: 0, staff: [] };
          }
          if (staff.staff_id != user.value.staff_id){
            groupedData.PM.teams[position].total += 1;
            if (staff.status_pm === 'OFFICE') {
              groupedData.PM.teams[position].inOffice += 1;
              groupedData.PM.inOffice += 1;
            } else {
              groupedData.PM.teams[position].wfh += 1;
              groupedData.PM.wfh += 1;
            }
            groupedData.PM.teams[position].staff.push({ staff_id: staff.staff_id, name: staff.name, status: staff.status_pm });
            }
        });



        selectedDateDetails.value = groupedData;
        expandedTeams.value = {}; // Reset expanded teams

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

async function displayDateDetails(dateStr) {
  const detailData = await fetchStaffScheduleDetail(dateStr);
  if (detailData) {
    let groupedData = {
      AM: {
        inOffice: 0,
        wfh: 0,
        total: detailData.staff.length - 1,
        teams: {}
      },
      PM: {
        inOffice: 0,
        wfh: 0,
        total: detailData.staff.length - 1,
        teams: {}
      }
    };

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

      // AM
      if (!groupedData.AM.teams[position]) {
        groupedData.AM.teams[position] = { teamName: position, inOffice: 0, wfh: 0, total: 0, staff: [] };
      }
      if (staff.staff_id != user.value.staff_id){
        groupedData.AM.teams[position].total += 1;
        if (staff.status_am === 'OFFICE') {
          groupedData.AM.teams[position].inOffice += 1;
          groupedData.AM.inOffice += 1;
        } else {
          groupedData.AM.teams[position].wfh += 1;
          groupedData.AM.wfh += 1;
        }
        groupedData.AM.teams[position].staff.push({ staff_id: staff.staff_id, name: staff.name, status: staff.status_am });
      }
      console.log(groupedData.AM.teams[position])

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

    selectedDateDetails.value = groupedData;
    expandedTeams.value = {}; // Reset expanded teams

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

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

async function initiateChunkedSummaryLoading() {
  isLoading.value = true;
  const today = new Date();

  const ranges = [];

  const currentMonthStart = new Date(today.getFullYear(), today.getMonth(), 1);
  const currentMonthEnd = new Date(today.getFullYear(), today.getMonth() + 1, 0);
  ranges.push({ start: new Date(currentMonthStart), end: new Date(currentMonthEnd) });

  for (let i = 1; i <= 2; i++) {
    const start = new Date(today.getFullYear(), today.getMonth() - i, 1);
    const end = new Date(today.getFullYear(), today.getMonth() - i + 1, 0);
    ranges.push({ start, end });
  }

  for (let i = 1; i <= 3; i++) {
    const start = new Date(today.getFullYear(), today.getMonth() + i, 1);
    const end = new Date(today.getFullYear(), today.getMonth() + i + 1, 0);
    ranges.push({ start, end });
  }

  for (let i = 0; i < ranges.length; i++) {
    if (i === 0) {
      await fetchStaffScheduleSummary(ranges[i].start, ranges[i].end);
    } else {
      setTimeout(async () => {
        await fetchStaffScheduleSummary(ranges[i].start, ranges[i].end);
      }, i * 1000);
    }
  }
  isLoading.value = false;
}

function toggleTeam(timeOfDay, teamName) {
  const key = `${timeOfDay}-${teamName}`;
  expandedTeams.value[key] = !expandedTeams.value[key];
}
</script>

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

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

.calendar-container {
  width: 70%;
  padding: 1rem;
  flex: 1;
  min-width: 300px;
  overflow-y: auto;
  position: relative;
}

.right-container {
  width: 30%;
  padding: 1.5rem;
  background-color: #f4f6f8;
  border-left: 1px solid #dfe3e8;
  overflow-y: auto;
  flex: none;
  min-width: 300px;
  height: 100%;
  position: relative;
  transition: transform 0.3s ease-in-out;
}

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

.details-container {
  display: flex;
  flex-direction: column;
}

.details-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 1.5rem;
}

.details-header h2 {
  margin: 0;
  font-size: 1.75rem;
  color: #2e3a59;
}

.close-button {
  background: none;
  border: none;
  font-size: 1.75rem;
  cursor: pointer;
  color: #888;
}

.tabs {
  display: flex;
  margin-bottom: 1rem;
}

.tab {
  flex: 1;
  padding: 0.75rem 1rem;
  background-color: #e0e7ff;
  border: none;
  cursor: pointer;
  font-weight: 600;
  color: #141b4d;
  transition: background-color 0.3s ease;
}

.tab:not(:last-child) {
  border-right: 1px solid #c7d2fe;
}

.tab.active {
  background-color: #141b4d;
  color: #ffffff;
}

.tab:hover {
  background-color: #c7d2fe;
}

.tab-content {
  flex: 1;
}

.summary {
  display: flex;
  justify-content: space-between;
  margin-bottom: 1rem;
}

.summary-item {
  display: flex;
  align-items: center;
}

.summary-label {
  font-weight: 600;
  margin-right: 0.5rem;
  color: #2e3a59;
}

.summary-value {
  font-weight: 500;
  color: #141b4d;
}

.teams {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.team {
  background-color: #ffffff;
  border: 1px solid #dfe3e8;
  border-radius: 8px;
  padding: 1rem;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
  cursor: pointer;
}

.team-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 0.75rem;
}

.team-header h3 {
  margin: 0;
  font-size: 1rem;
  color: #2e3a59;
}

.badge-container {
  display: flex;
  gap: 0.5rem;
}

.badge {
  display: inline-block;
  padding: 0.25rem 0.75rem;
  border-radius: 12px;
  font-size: 0.85em;
  color: #ffffff;
}

.badge-office {
  background-color: #6BCB77; /* Green */
}

.badge-wfh {
  background-color: #FFD93D; /* Yellow */
  color: #000000; /* Black text for better visibility */
}

.staff-list {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.staff-card {
  display: flex;
  align-items: center;
  border: 1px solid #dfe3e8;
  border-radius: 6px;
  padding: 0.5rem 0.75rem;
  background-color: #f9fafb;
}

.status-indicator {
  display: inline-block;
  width: 12px;
  height: 12px;
  border-radius: 50%;
  margin-right: 0.75rem;
}

.status-indicator.green {
  background-color: #6BCB77;
}

.status-indicator.yellow {
  background-color: #FFD93D;
}

.staff-name {
  font-weight: 500;
  color: #2e3a59;
  font-size: 0.8rem;

}

.no-selection {
  text-align: center;
  color: #888;
  margin-top: 2rem;
  font-size: 1.1rem;
}

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

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

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

.calendar-event {
  /* Additional styling if needed */
}

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

/* Adjust the calendar height */
.fc .fc-view-harness {
  min-height: 0;
}

/* Hide the scrollbar but allow scrolling */
.calendar-container::-webkit-scrollbar {
  width: 0;
  height: 0;
}

.right-container::-webkit-scrollbar {
  width: 8px;
}

.right-container::-webkit-scrollbar-thumb {
  background-color: rgba(0, 0, 0, 0.2);
  border-radius: 4px;
}

.right-container::-webkit-scrollbar-track {
  background-color: transparent;
}

/* Media query for responsive layout */
@media (max-width: 768px) {
  .calendar-container {
    width: 100%;
  }

  .right-container {
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;
    background-color: rgba(255, 255, 255, 0.95);
    transform: translateX(100%);
    transition: transform 0.3s ease-in-out;
    z-index: 1000;
    overflow-y: auto;
    padding: 1.5rem;
  }

  .right-container.show {
    transform: translateX(0);
  }

  /* Ensure text within badges remains black on yellow backgrounds */
  .badge-wfh, .badge-wfh-calendar {
    color: #000000 !important;
  }
}

@media (max-width: 768px) {
  .main-section {
    flex-direction: column;
  }

  .right-container {
    width: 100%;
    min-width: unset;
    height: auto;
    position: fixed;
    right: 0;
    top: 0;
    box-shadow: none;
    background-color: #ffffff;
  }

  .calendar-container {
    width: 100%;
    height: 100%;
  }

  .details-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
  }

  .tabs {
    flex-direction: row;
    justify-content: space-between;
  }
}

/* New Styles for Conditional Text Colors */
.yellow-event {
  color: #000000 !important;
}

.default-event {
  color: #ffffff !important;
}

.yellow-event .badge,
.default-event .badge {
  color: inherit !important;
}

/* Loading Overlay Styles */
.loading-overlay {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: rgba(255, 255, 255, 0.8);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 10;
}

.spinner {
  border: 8px solid #f3f3f3; /* Light grey */
  border-top: 8px solid #3498db; /* Blue */
  border-radius: 50%;
  width: 60px;
  height: 60px;
  animation: spin 2s linear infinite;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

/* Indicate that team cards are clickable */
.team.card {
  transition: background-color 0.2s ease;
}

.team.card:hover {
  background-color: #f0f4ff;
}
</style>