<template>
    <div class="container">
      <!-- Timeline Header -->
      <div class="timeline-header" ref="timelineHeader">
        <!-- Time labels will be injected here via JavaScript -->
      </div>
  
      <!-- Grid Area with Horizontal Scroll -->
      <div class="grid-area-container" ref="gridAreaContainer">
        <div class="grid-area" ref="gridArea">
          <!-- Draggable and Resizable Boxes -->
          <div
            v-for="box in boxes"
            :key="box.id"
            class="draggable-box"
            :style="{
              left: box.left + 'px',
              top: box.top + 'px',
              width: box.width + 'px',
              height: box.height + 'px',
            }"
            @mousedown="onBoxMouseDown($event, box)"
          >
            <span class="box-label">{{ box.label }}</span>
            <div
              class="resize-handle left"
              @mousedown.stop.prevent="onResizeMouseDown($event, box, 'left')"
            ></div>
            <div
              class="resize-handle right"
              @mousedown.stop.prevent="onResizeMouseDown($event, box, 'right')"
            ></div>
          </div>
  
          <!-- Vertical Guide Line for Resizing -->
          <div class="vertical-guide" ref="verticalGuide"></div>
          <!-- Time Label for Resizing -->
          <div class="time-label-resize" ref="timeLabelResize"></div>
          <!-- Current Time Line -->
          <div class="current-time-line" ref="currentTimeLine">
            <div class="current-time-label" ref="currentTimeLabel">Now</div>
          </div>
        </div>
      </div>
    </div>
  </template>
  
  <script>
  export default {
    name: "FullDayTimeline",
    data() {
      return {
        // Define the draggable and resizable boxes
        boxes: [
          { id: "box1", label: "Box 1", left: 600, top: 20, width: 120, height: 40 },
          { id: "box2", label: "Box 2", left: 1800, top: 80, width: 180, height: 40 },
        ],
        // References to handle dragging and resizing
        draggingBox: null,
        initialMouseX: 0,
        initialMouseY: 0,
        initialBoxLeft: 0,
        initialBoxTop: 0,
        resizingBox: null,
        resizeDirection: null,
        initialBoxState: {},
        // Base start time is fixed at midnight
        baseStartTime: null,
        // Interval ID for updating the current time line
        currentTimeInterval: null,
        // Event handlers
        handleBoxMouseMove: null,
        handleBoxMouseUp: null,
        handleResizeMouseMove: null,
        handleResizeMouseUp: null,
        handleGridScroll: null,
      };
    },
    mounted() {
      this.initializeTimeline();
      this.setupCurrentTimeLine();
      this.scrollToCurrentTime();
  
      // Synchronize timeline header scroll with grid area scroll
      this.handleGridScroll = () => {
        this.$refs.timelineHeader.scrollLeft = this.$refs.gridAreaContainer.scrollLeft;
      };
      this.$refs.gridAreaContainer.addEventListener("scroll", this.handleGridScroll);
  
      window.addEventListener("resize", this.onWindowResize);
    },
    beforeUnmount() {
      window.removeEventListener("resize", this.onWindowResize);
      if (this.currentTimeInterval) {
        clearInterval(this.currentTimeInterval);
      }
      // Clean up any lingering event listeners
      document.removeEventListener("mousemove", this.handleBoxMouseMove);
      document.removeEventListener("mouseup", this.handleBoxMouseUp);
      document.removeEventListener("mousemove", this.handleResizeMouseMove);
      document.removeEventListener("mouseup", this.handleResizeMouseUp);
      this.$refs.gridAreaContainer.removeEventListener("scroll", this.handleGridScroll);
    },
    methods: {
      /**
       * Initializes the timeline by setting the base start time
       * and creating the time labels.
       */
      initializeTimeline() {
        this.baseStartTime = this.getStartOfDay(new Date());
        this.createTimelineHeaders();
      },
  
      /**
       * Returns the start of the day for a given date.
       * @param {Date} date - The date object.
       * @returns {Date} - Date set to midnight.
       */
      getStartOfDay(date) {
        const start = new Date(date);
        start.setHours(0, 0, 0, 0);
        return start;
      },
  
      /**
       * Creates time labels for the entire day.
       * Major labels every hour and minor labels every 15 minutes.
       */
      createTimelineHeaders() {
        const timelineHeader = this.$refs.timelineHeader;
        timelineHeader.innerHTML = ""; // Clear existing labels
        const totalMinutes = 24 * 60; // Total minutes in a day
  
        for (let i = 0; i <= totalMinutes; i++) {
          // Create major labels every hour
          if (i % 60 === 0) {
            const time = this.formatTime(this.baseStartTime, i);
            const timeLabel = document.createElement("div");
            timeLabel.classList.add("time-label", "major");
            timeLabel.style.left = `${i * 10}px`; // 10px per minute
            timeLabel.textContent = time;
            timelineHeader.appendChild(timeLabel);
          }
          // Create minor labels every 15 minutes
          else if (i % 15 === 0) {
            const time = this.formatTime(this.baseStartTime, i);
            const timeLabel = document.createElement("div");
            timeLabel.classList.add("time-label", "minor");
            timeLabel.style.left = `${i * 10}px`; // 10px per minute
            timeLabel.textContent = time;
            timelineHeader.appendChild(timeLabel);
          }
        }
      },
  
      /**
       * Formats time based on baseStartTime and offset minutes.
       * @param {Date} baseTime - The base start time (midnight).
       * @param {number} minutesOffset - Minutes to add.
       * @returns {string} - Formatted time string.
       */
      formatTime(baseTime, minutesOffset) {
        const time = new Date(baseTime.getTime());
        time.setMinutes(time.getMinutes() + minutesOffset);
        return time.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
      },
  
      /**
       * Handles the mousedown event on a draggable box to initiate dragging.
       * @param {MouseEvent} e - The mousedown event.
       * @param {Object} box - The box data object.
       */
      onBoxMouseDown(e, box) {
        this.draggingBox = box;
        this.initialMouseX = e.clientX;
        this.initialMouseY = e.clientY;
        this.initialBoxLeft = box.left;
        this.initialBoxTop = box.top;
  
        // Define event handlers
        this.handleBoxMouseMove = (e) => {
          this.onBoxMouseMove(e);
        };
        this.handleBoxMouseUp = () => {
          this.onBoxMouseUp();
        };
  
        document.addEventListener("mousemove", this.handleBoxMouseMove);
        document.addEventListener("mouseup", this.handleBoxMouseUp);
      },
  
      onBoxMouseMove(e) {
        if (!this.draggingBox) return;
  
        // Calculate the difference between current mouse position and initial position
        const deltaX = e.clientX - this.initialMouseX;
        const deltaY = e.clientY - this.initialMouseY;
  
        // Calculate new positions
        let newLeft = this.initialBoxLeft + deltaX;
        let newTop = this.initialBoxTop + deltaY;
  
        // Snap to grid (10px per minute horizontally, 10px per unit vertically)
        newLeft = this.snapToGrid(newLeft, 10);
        newTop = this.snapToGrid(newTop, 10);
  
        // Boundaries
        newLeft = Math.max(
          0,
          Math.min(this.$refs.gridArea.offsetWidth - this.draggingBox.width, newLeft)
        );
        newTop = Math.max(
          0,
          Math.min(this.$refs.gridArea.offsetHeight - this.draggingBox.height, newTop)
        );
  
        // Update box position
        this.draggingBox.left = newLeft;
        this.draggingBox.top = newTop;
      },
  
      /**
       * Handles the mouseup event to stop dragging.
       */
      onBoxMouseUp() {
        this.draggingBox = null;
        document.removeEventListener("mousemove", this.handleBoxMouseMove);
        document.removeEventListener("mouseup", this.handleBoxMouseUp);
      },
  
      /**
       * Handles the mousedown event on a resize handle to initiate resizing.
       * @param {MouseEvent} e - The mousedown event.
       * @param {Object} box - The box data object.
       * @param {string} direction - The resize direction ('left' or 'right').
       */
      onResizeMouseDown(e, box, direction) {
        this.resizingBox = box;
        this.resizeDirection = direction;
        this.initialBoxState = { ...box };
        this.startX = e.clientX;
  
        // Show the vertical guide and time label
        this.$refs.verticalGuide.style.display = "block";
        this.$refs.timeLabelResize.style.display = "block";
  
        // Initial position of the vertical guide
        const guidePosition =
          direction === "right" ? box.left + box.width : box.left;
        this.$refs.verticalGuide.style.left = `${guidePosition}px`;
        this.$refs.timeLabelResize.textContent = this.getTimeFromPosition(
          guidePosition / 10
        );
        const labelOffsetX =
          direction === "right"
            ? 5
            : -5 - this.$refs.timeLabelResize.offsetWidth;
        this.$refs.timeLabelResize.style.left = `${guidePosition + labelOffsetX}px`;
        this.$refs.timeLabelResize.style.top = `${box.top + box.height / 2}px`;
  
        // Define event handlers
        this.handleResizeMouseMove = (e) => {
          this.onResizeMouseMove(e);
        };
        this.handleResizeMouseUp = () => {
          this.onResizeMouseUp();
        };
  
        document.addEventListener("mousemove", this.handleResizeMouseMove);
        document.addEventListener("mouseup", this.handleResizeMouseUp);
      },
  
      /**
       * Handles the mousemove event during resizing.
       * @param {MouseEvent} e - The mousemove event.
       */
      onResizeMouseMove(e) {
        if (!this.resizingBox) return;
        const deltaX = e.clientX - this.startX;
        let newWidth = this.resizingBox.width;
        let newLeft = this.resizingBox.left;
  
        if (this.resizeDirection === "right") {
          newWidth = this.snapToGrid(this.initialBoxState.width + deltaX, 10);
          newWidth = Math.max(10, newWidth); // Minimum width of 1 minute (10px)
        } else if (this.resizeDirection === "left") {
          newWidth = this.snapToGrid(this.initialBoxState.width - deltaX, 10);
          newWidth = Math.max(10, newWidth); // Minimum width of 1 minute (10px)
          newLeft = this.snapToGrid(
            this.initialBoxState.left + deltaX,
            10
          );
        }
  
        // Boundaries
        if (newLeft < 0) {
          newLeft = 0;
          newWidth = this.snapToGrid(
            this.initialBoxState.width + this.initialBoxState.left,
            10
          );
        }
        if (newLeft + newWidth > this.$refs.gridArea.offsetWidth) {
          newWidth = this.snapToGrid(
            this.$refs.gridArea.offsetWidth - newLeft,
            10
          );
        }
  
        // Update box dimensions
        this.resizingBox.width = newWidth;
        this.resizingBox.left = newLeft;
  
        // Update the vertical guide position
        const guidePosition =
          this.resizeDirection === "right" ? newLeft + newWidth : newLeft;
        this.$refs.verticalGuide.style.left = `${guidePosition}px`;
  
        // Update the time label
        const displayTime = this.getTimeFromPosition(guidePosition / 10);
        this.$refs.timeLabelResize.textContent = displayTime;
  
        if (this.resizeDirection === "right") {
          this.$refs.timeLabelResize.style.left = `${guidePosition + 5}px`; // 5px offset
        } else if (this.resizeDirection === "left") {
          this.$refs.timeLabelResize.style.left = `${
            guidePosition - 5 - this.$refs.timeLabelResize.offsetWidth
          }px`; // 5px left offset
        }
        this.$refs.timeLabelResize.style.top = `${
          this.resizingBox.top + this.resizingBox.height / 2
        }px`;
      },
  
      /**
       * Handles the mouseup event to stop resizing.
       */
      onResizeMouseUp() {
        this.resizingBox = null;
        this.resizeDirection = null;
        this.initialBoxState = {};
        this.$refs.verticalGuide.style.display = "none";
        this.$refs.timeLabelResize.style.display = "none";
  
        document.removeEventListener("mousemove", this.handleResizeMouseMove);
        document.removeEventListener("mouseup", this.handleResizeMouseUp);
      },
  
      /**
       * Sets up the current time line and updates its position every minute.
       */
      setupCurrentTimeLine() {
        this.updateCurrentTimeLine();
        this.currentTimeInterval = setInterval(this.updateCurrentTimeLine, 60000); // Update every minute
        // Initial display
        this.$refs.currentTimeLine.style.display = "block";
      },
  
      /**
       * Updates the current time line position based on the current time.
       */
      updateCurrentTimeLine() {
        const now = new Date();
        const minutesSinceStart = this.getMinutesSinceStartOfDay(now);
        const currentX = this.snapToGrid(minutesSinceStart * 10, 10); // 10px per minute
  
        // Define the start and end times based on gridArea
        const totalGridWidth = this.$refs.gridArea.offsetWidth; // in pixels
  
        if (currentX >= 0 && currentX <= totalGridWidth) {
          this.$refs.currentTimeLine.style.left = `${currentX}px`;
          this.$refs.currentTimeLine.style.display = "block";
  
          // Update label
          const currentTime = now.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
          this.$refs.currentTimeLabel.textContent = `Now - ${currentTime}`;
  
          // Position the label above the timeline header
          this.$refs.currentTimeLabel.style.left = `${currentX + 5}px`; // 5px offset to the right
          this.$refs.currentTimeLabel.style.top = `-${this.$refs.timelineHeader.offsetHeight + 10}px`; // Position above the timeline header with 10px margin
        } else {
          // Hide the line if current time is outside the container's timeline range
          this.$refs.currentTimeLine.style.display = "none";
        }
      },
  
      /**
       * Snaps a given value to the nearest grid size.
       * @param {number} value - The value to snap.
       * @param {number} gridSize - The grid size in pixels.
       * @returns {number} - The snapped value.
       */
      snapToGrid(value, gridSize) {
        return Math.round(value / gridSize) * gridSize;
      },
  
      /**
       * Handles window resize events to recalculate the timeline and current time line.
       */
      onWindowResize() {
        // Recreate timeline headers
        this.createTimelineHeaders();
        // Reposition current time line
        this.updateCurrentTimeLine();
        // Optional: Re-scroll to current time to maintain visibility
        this.scrollToCurrentTime();
      },
  
      /**
       * Converts grid position in minutes to a formatted time string.
       * @param {number} minutes - Minutes since midnight.
       * @returns {string} - Formatted time string.
       */
      getTimeFromPosition(minutes) {
        const time = new Date(this.baseStartTime.getTime());
        time.setMinutes(time.getMinutes() + minutes);
        return time.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" });
      },
  
      /**
       * Calculates the number of minutes since the start of the day.
       * @param {Date} date - The current date and time.
       * @returns {number} - Minutes since midnight.
       */
      getMinutesSinceStartOfDay(date) {
        return date.getHours() * 60 + date.getMinutes();
      },
  
      /**
       * Scrolls the grid area to center the current time.
       */
      scrollToCurrentTime() {
        const now = new Date();
        const minutesSinceStart = this.getMinutesSinceStartOfDay(now);
        const currentX = minutesSinceStart * 10; // 10px per minute
  
        // Calculate the center position
        const containerWidth = this.$refs.gridAreaContainer.offsetWidth;
        const scrollPosition = currentX - containerWidth / 2;
  
        // Scroll within boundaries
        const maxScroll = this.$refs.gridArea.scrollWidth - containerWidth;
        const finalScrollPosition = Math.max(0, Math.min(scrollPosition, maxScroll));
  
        this.$refs.gridAreaContainer.scrollLeft = finalScrollPosition;
      },
    },
  };
  </script>
  
  <style scoped>
  .container {
    position: relative;
    width: 100%;
    height: 100%;
    background-color: #fff;
    border: 1px solid #ddd;
    display: flex;
    flex-direction: column;
    overflow: hidden;
  }
  
  /* Timeline Header */
  .timeline-header {
    position: relative;
    height: 40px;
    background-color: #fafafa;
    border-bottom: 1px solid #ddd;
    display: flex;
    align-items: center;
    padding-left: 10px;
    box-sizing: border-box;
    flex-shrink: 0;
    white-space: nowrap;
    overflow: hidden; /* Removed horizontal scroll */
    position: sticky;
    top: 0;
    z-index: 100; /* Ensures the header stays above other elements */
  }
  
  .timeline-header .time-label {
    position: absolute;
    top: 5px;
    font-size: 10px; /* Reduced font size from 12px to 10px */
    color: #555;
    white-space: nowrap;
    transform: translateX(-50%);
    pointer-events: none;
  }
  
  .timeline-header .time-label.major {
    font-weight: bold;
    color: #000;
  }
  
  .timeline-header .time-label.minor {
    font-weight: normal;
    color: #555;
  }
  
  /* Grid Area with Horizontal Scroll */
  .grid-area-container {
    position: relative;
    flex-grow: 1;
    overflow-x: scroll; /* Only grid area has scroll */
    overflow-y: hidden;
  }
  
  .grid-area {
    position: relative;
    height: 100%;
    width: 14400px; /* 24 hours * 60 minutes * 10px per minute */
    background-image:
      linear-gradient(to right, #e0e0e0 1px, transparent 1px),
      linear-gradient(to bottom, #e0e0e0 1px, transparent 1px);
    background-size: 10px 10px, 100% 10px; /* Minor grid lines every minute, major every hour */
  }
  
  /* Draggable and Resizable Boxes */
  .draggable-box {
    position: absolute;
    background-color: #4caf50;
    color: #fff;
    padding: 5px;
    cursor: move;
    box-sizing: border-box;
    border: 1px solid #000;
    display: flex;
    align-items: center;
    border-radius: 4px;
  }
  
  .box-label {
    flex-grow: 1;
    user-select: none;
    padding-right: 5px;
    font-size: 14px;
  }
  
  /* Resize Handles */
  .resize-handle {
    width: 8px;
    background-color: #555;
    cursor: col-resize;
    height: 100%;
    position: absolute;
    top: 0;
  }
  
  .resize-handle.left {
    left: 0;
    border-top-left-radius: 4px;
    border-bottom-left-radius: 4px;
  }
  
  .resize-handle.right {
    right: 0;
    border-top-right-radius: 4px;
    border-bottom-right-radius: 4px;
  }
  
  /* Vertical Guide Line for Resizing */
  .vertical-guide {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 2px;
    background-color: red;
    display: none;
    z-index: 10;
  }
  
  /* Time Label for Resizing */
  .time-label-resize {
    position: absolute;
    background-color: #fff;
    padding: 2px 5px;
    border: 1px solid #ddd;
    display: none;
    font-size: 12px;
    white-space: nowrap;
    z-index: 11;
    border-radius: 3px;
  }
  
  /* Current Time Line */
  .current-time-line {
    position: absolute;
    top: 0;
    bottom: 0;
    width: 2px;
    background-color: blue;
    display: none;
    z-index: 5;
  }
  
  .current-time-label {
    position: absolute;
    background-color: #fff;
    padding: 2px 5px;
    border: 1px solid #ddd;
    font-size: 12px;
    white-space: nowrap;
    z-index: 6;
    border-radius: 3px;
  }
  </style>
consider each box represents a task. i want a column for task to the left of the grid