From 7a7042c3f5efb1739628201d38f709a6b7ae991b Mon Sep 17 00:00:00 2001
From: Kritkhanin Anantakul <65160144@go.buu.ac.th>
Date: Tue, 18 Mar 2025 16:41:16 +0700
Subject: [PATCH] all gantt service store / connect fontend backend / fix
 calendar format/ fix some ui

---
 src/components/GanttChart/GanttChart.vue | 358 +++++++++++------------
 1 file changed, 172 insertions(+), 186 deletions(-)

diff --git a/src/components/GanttChart/GanttChart.vue b/src/components/GanttChart/GanttChart.vue
index 839de80..a732f38 100644
--- a/src/components/GanttChart/GanttChart.vue
+++ b/src/components/GanttChart/GanttChart.vue
@@ -4,9 +4,8 @@
     <div class="gantt-header">
       <!-- Calendar: เชื่อมกับ selectedDate -->
       <GanttCalendar v-model:modelValue="selectedDate" />
-
       <!-- ปุ่ม Make a queue พร้อม event listener -->
-      <MakequeueBtn @click="queueStore.makeQueue()" />
+      <MakequeueBtn @click="openAddQueueDialog" />
     </div>
 
     <!-- Gantt Chart UI -->
@@ -21,31 +20,48 @@
         </div>
       </div>
 
-      <!-- Rows: เครื่องจักรแต่ละตัว -->
+      <!-- Rows: แสดงเครื่องจักรจาก Machine Store -->
       <div class="rows">
-        <div v-for="machine in machines" :key="machine.machineID" class="row" @dragover.prevent="onDragOver($event)"
-          @drop="onDrop($event, machine.machineID)">
+        <div
+          v-for="machine in machineStore.machines"
+          :key="machine.MachineID"
+          class="row"
+          :data-machine-id="machine.MachineID"
+          @dragover.prevent="onDragOver($event)"
+          @drop="onDrop($event, machine.MachineID)"
+        >
           <div class="machine-label">
-            {{ machine.machineID }}
+            {{ machine.name }}
           </div>
           <div class="row-timeline">
             <!-- เส้นแนวตั้ง (Grid Lines) -->
-            <div v-for="hour in hours" :key="'line-' + hour" class="vertical-line" :style="getLineStyle(hour)"></div>
+            <div
+              v-for="hour in hours"
+              :key="'line-' + hour"
+              class="vertical-line"
+              :style="getLineStyle(hour)"
+            ></div>
 
             <!-- แสดง Queue ที่ตรงกับวันที่ (selectedDate), Page, และ Machine -->
-            <div v-for="item in filteredQueue(machine.machineID)" :key="item.queueID" class="order"
-              :class="{ 'faded': draggingQueue && draggingQueue.queueID === item.queueID }"
-              :style="getQueueStyle(item)">
-
+            <div
+              v-for="item in filteredQueue(machine.MachineID)"
+              :key="item.QueueID"
+              class="order"
+              :class="{ faded: draggingQueue && draggingQueue.QueueID === item.QueueID }"
+              :style="getQueueStyle(item)"
+            >
               <!-- Handle สำหรับ Resize ด้านซ้าย -->
               <div class="resize-handle left" @mousedown="onResizeStart($event, item, 'left')"></div>
-
               <!-- ส่วนกลางของ Order ใช้สำหรับลาก และเปิด Dialog เมื่อคลิก -->
-              <div class="order-content" draggable="true" @dragstart="onDragStart($event, item)"
-                @dragend="onDragEnd($event, item)" @click.stop="openQueueDialog(item)">
+              <div
+                class="order-content"
+                draggable="true"
+                @dragstart="onDragStart($event, item)"
+                @dragend="onDragEnd($event, item)"
+                @click.stop="openQueueDialog(item)"
+              >
                 {{ item.orderID }} ({{ getTimeString(item.startTime) }} - {{ getTimeString(item.finishTime) }})
               </div>
-
               <!-- Handle สำหรับ Resize ด้านขวา -->
               <div class="resize-handle right" @mousedown="onResizeStart($event, item, 'right')"></div>
             </div>
@@ -53,10 +69,9 @@
         </div>
       </div>
 
-      <!-- Ghost Queue (ขณะลาก) -->
+      <!-- Ghost Queue (ขณะลาก/resize) -->
       <div v-if="ghostQueue" class="drag-ghost" :style="ghostStyle">
-        {{ ghostQueue.orderID }} ({{ getTimeString(ghostQueue.startTime) }} - {{ getTimeString(ghostQueue.finishTime)
-        }})
+        {{ ghostQueue.orderID }} ({{ getTimeString(ghostQueue.startTime) }} - {{ getTimeString(ghostQueue.finishTime) }})
       </div>
 
       <v-divider :thickness="7"></v-divider>
@@ -64,88 +79,81 @@
       <!-- Pagination -->
       <div class="pagination-container">
         <div class="pagination">
-          <!-- ปุ่มเลขหน้า -->
-          <button v-for="p in pages" :key="p" :class="['page-btn', { active: p === currentPage }]"
-            @click="currentPage = p" @contextmenu.prevent="onPageRightClick(p, $event)">
+          <button
+            v-for="p in pages"
+            :key="p"
+            :class="['page-btn', { active: p === currentPage }]"
+            @click="currentPage = p"
+            @contextmenu.prevent="onPageRightClick(p, $event)"
+          >
             {{ p }}
             <button v-if="pageToShowDelete === p" class="delete-btn" @click.stop="deletePage(p)">
               Delete
             </button>
           </button>
-
-          <!-- ปุ่มเพิ่มหน้า + (แบบปกติ) -->
           <button class="page-btn add-page" @click="addPage" :disabled="pages.length >= 10">
             +
           </button>
         </div>
-
-        <!-- ปุ่ม Add Queue อยู่ขวาสุด -->
         <div class="pagination-right">
           <v-btn class="add-page-btn" @click="openAddQueueDialog" icon>
             <v-icon>mdi-plus</v-icon>
           </v-btn>
         </div>
-
-        <!-- AddQueueDialog (สำหรับเพิ่ม/แก้ไข) -->
-        <AddQueueDialog :visible="showAddDialog" :queueItem="selectedQueueItem" @close="closeAddQueueDialog"
-          @add="handleAddQueue" @edit="handleEditQueue" />
+        <AddQueueDialog
+          :visible="showAddDialog"
+          :queueItem="selectedQueueItem"
+          @close="closeAddQueueDialog"
+          @add="handleAddQueue"
+          @edit="handleEditQueue"
+        />
       </div>
-
-      <!-- Order Dialog (สำหรับดูรายละเอียดเพิ่มเติม) -->
-      <OrderDialog v-if="selectedQueueItem && !showAddDialog" :queueItem="selectedQueueItem"
-        :color="getQueueColor(selectedQueueItem.orderID)" @close="closeQueueDialog" @edit="openAddQueueDialogForEdit"
-        @delete="deleteQueueItem" />
+      <OrderDialog
+        v-if="selectedQueueItem && !showAddDialog"
+        :queueItem="selectedQueueItem"
+        :color="getQueueColor(selectedQueueItem.orderID)"
+        @close="closeQueueDialog"
+        @edit="openAddQueueDialogForEdit"
+        @delete="deleteQueueItem"
+      />
     </div>
   </div>
 </template>
 
 <script lang="ts" setup>
 import { ref, reactive, computed, onMounted, onBeforeUnmount, watch } from 'vue';
+import { useQueueStore } from '@/stores/queue';
+import { useMachineStore } from '@/stores/machine';
 import GanttCalendar from './GanttCalendar.vue';
 import OrderDialog from './OrderDialog.vue';
 import AddQueueDialog from './AddQueueDialog.vue';
 import MakequeueBtn from './MakequeueBtn.vue';
 import './ganttChart.css';
-import { useQueueStore } from '@/stores/queue';
-import type { QueueItem } from '@/types/QueueItem';
-
-interface Machine {
-  machineID: string;
-  name: string;
-}
-
-
 
+// ใช้ store สำหรับ Queue และ Machine
 const queueStore = useQueueStore();
+const machineStore = useMachineStore();
 
-// NOTE: ถ้าต้องการโหลดข้อมูลทันทีที่เข้าหน้านี้ ให้ un-comment ด้านล่าง
+// เมื่อเข้าหน้า ให้ดึงข้อมูลจาก backend
 onMounted(() => {
-
+  machineStore.fetchMachines();
+  queueStore.fetchQueues().then(() => {
+    console.log("📌 Queues Loaded:", queueStore.queues);
+  });
 });
 
-// State
+// State สำหรับ Gantt Chart
 const showAddDialog = ref(false);
-const selectedDate = ref('1/1/2025');
+const selectedDate = ref('2025-01-01');
 const startTime = ref('08:00');
 const endTime = ref('17:00');
 
-const machines = ref<Machine[]>([
-  { machineID: 'MC1', name: 'เครื่องเป่าขวด' },
-  { machineID: 'MC2', name: 'เครื่องเป่าขวด2' },
-  { machineID: 'MC3', name: 'เครื่องสวมฉลาก' },
-  { machineID: 'MC4', name: 'เครื่องบรรจุ+แพ็ค' },
-]);
-
-// ดึง ref ของ QueueItem[] จาก Store
-const Queues = computed(() => queueStore.Queues.value);
-
-// Drag/Drop & Resize
-const draggingQueue = ref<QueueItem | null>(null);
+const draggingQueue = ref<any | null>(null);
 const dragOffset = ref(0);
-const resizingQueue = ref<QueueItem | null>(null);
+const resizingQueue = ref<any | null>(null);
 const resizeDirection = ref<string | null>(null);
 
-const ghostQueue = ref<QueueItem | null>(null);
+const ghostQueue = ref<any | null>(null);
 const ghostStyle = reactive({
   position: 'fixed',
   top: '0px',
@@ -162,19 +170,42 @@ const ghostStyle = reactive({
   zIndex: 9999,
 });
 
+// แปลงข้อมูล Queue จาก backend ให้เข้ากับรูปแบบที่ Gantt Chart ใช้
+const formattedQueues = computed(() => {
+  return queueStore.queues.map(q => ({
+    QueueID: q.QueueID,
+    orderID: q.order?.OrderID || "Unknown",
+    machineID: q.machine?.MachineID || 0,
+    machineName: q.machine?.name || "Unknown Machine",
+    startTime: q.startTime ? convertToLocalTime(q.startTime) : "Unknown",
+    finishTime: q.finishTime ? convertToLocalTime(q.finishTime) : "Unknown",
+    status: q.status || "Unknown",
+    bottleSize: q.bottleSize || "Unknown",
+    producedQuantity: q.producedQuantity || 0,
+    pageNumber: q.page?.PageID || 0,
+  }));
+});
+
+function convertToLocalTime(utcString: string): string {
+  const date = new Date(utcString);
+  date.setHours(date.getHours() + 7);
+  return date.toISOString().slice(0, 16).replace("T", " ");
+}
+
 // Pagination & Dialog
 const pages = ref<number[]>([1, 2]);
 const currentPage = ref(1);
 const pageToShowDelete = ref<number | null>(null);
-const selectedQueueItem = ref<QueueItem | null>(null);
-
+const selectedQueueItem = ref<any | null>(null);
 
-watch(() => queueStore.Queues.value, (newVal) => {
-  console.log("🔄 Queues updated in GanttChart.vue:", newVal);
-}, { deep: true });
+watch(
+  () => queueStore.queues,
+  (newVal) => {
+    console.log("🔄 Queues updated in GanttChart.vue:", newVal);
+  },
+  { deep: true }
+);
 
-
-// Computed
 const hours = computed(() => {
   const startHour = parseInt(startTime.value.split(':')[0]);
   const endHour = parseInt(endTime.value.split(':')[0]);
@@ -182,78 +213,51 @@ const hours = computed(() => {
 });
 
 // Methods
-function handleUpdateQueue(newQueue: QueueItem[]) {
-  console.log("Received new queue:", newQueue);
-  // อัปเดตลงใน store
-  Queues.value = newQueue;
-}
-
-function openQueueDialog(item: QueueItem) {
+function openQueueDialog(item: any) {
   selectedQueueItem.value = { ...item };
 }
-
 function closeQueueDialog() {
   selectedQueueItem.value = null;
 }
-
 function openAddQueueDialog() {
   selectedQueueItem.value = null;
   showAddDialog.value = true;
 }
-
-function deleteQueueItem(item: QueueItem) {
-  // ลบคิวใน store
-  Queues.value = Queues.value.filter(q => q.queueID !== item.queueID);
+function deleteQueueItem(item: any) {
+  queueStore.removeQueue(item.QueueID);
   closeQueueDialog();
 }
-
-function openAddQueueDialogForEdit(queueItem: QueueItem) {
+function openAddQueueDialogForEdit(queueItem: any) {
   selectedQueueItem.value = { ...queueItem };
   showAddDialog.value = true;
 }
-
 function closeAddQueueDialog() {
   showAddDialog.value = false;
   selectedQueueItem.value = null;
 }
-
-function handleAddQueue(newQueueItem: Omit<QueueItem, 'queueID'>) {
-  const newQueueID = Queues.value.length + 1;
-  Queues.value.push({ queueID: newQueueID, ...newQueueItem });
+async function handleAddQueue(newQueueItem: any) {
+  await queueStore.addQueue(newQueueItem);
   showAddDialog.value = false;
 }
-
-function handleEditQueue(updatedQueueItem: QueueItem) {
-  const index = Queues.value.findIndex(q => q.queueID === updatedQueueItem.queueID);
-  if (index !== -1) {
-    Queues.value.splice(index, 1, { ...updatedQueueItem });
-  }
+async function handleEditQueue(updatedQueueItem: any) {
+  await queueStore.editQueue(updatedQueueItem.QueueID, updatedQueueItem);
   selectedQueueItem.value = null;
   showAddDialog.value = false;
 }
 
-// Utility Functions
 function formatHour(hour: number): string {
   return (hour < 10 ? '0' + hour : hour) + ':00';
 }
-
 function getDateString(dateTimeStr: string): string {
   return dateTimeStr.split(' ')[0];
 }
-
 function getTimeString(dateTimeStr: string): string {
   return dateTimeStr.split(' ')[1];
 }
 
-function filteredQueue(machineID: string) {
-  if (!queueStore.Queues?.values || typeof queueStore.Queues.values !== "function") {
-    console.warn("queueStore.Queues.values is not a function or is undefined");
-    return [];
-  }
-
-  return queueStore.Queues.filter(q => {
+function filteredQueue(machineID: number) {
+  return formattedQueues.value.filter(q => {
     const queueDate = getDateString(q.startTime);
-    console.log(queueDate)
     return (
       q.machineID === machineID &&
       q.pageNumber === currentPage.value &&
@@ -262,59 +266,36 @@ function filteredQueue(machineID: string) {
   });
 }
 
-
-
-
-const colorMap: Record<string, string> = {}; // Map เก็บสีของแต่ละ orderID
-
+const colorMap: Record<string, string> = {};
 function getQueueColor(orderID: string | number): string {
   const key = String(orderID);
-
-  // 🔴 ถ้าเป็น "ผลิตเผื่อ" → ใช้สีแดงพาสเทล
-  if (key === "ผลิตเผื่อ") {
-    return "#FFC1C1"; // Light Pastel Red
-  }
-
-  // 💜 ถ้าเป็น "เปลี่ยนขนาด" → ใช้สีม่วงชมพูพาสเทล
-  if (key === "เปลี่ยนขนาด") {
-    return "#E1BEE7"; // Light Pastel Pink-Purple (Lavender)
-  }
-
-  // ✅ ถ้ามีสีอยู่แล้ว ให้ใช้สีเดิม
+  if (key === "ผลิตเผื่อ") return "#FFC1C1";
+  if (key === "เปลี่ยนขนาด") return "#E1BEE7";
   if (colorMap[key]) return colorMap[key];
-
-  // 🌊 ถ้าเป็นคิวปกติ → ใช้สีธีมน้ำ
   const newColor = generateWaterPastel();
   colorMap[key] = newColor;
   return newColor;
 }
-
 function generateWaterPastel(): string {
-  // สุ่มเฉพาะสีโทนน้ำทะเล (ฟ้า, น้ำเงิน, เขียวมิ้นต์)
   const waterHues = [180, 190, 200, 210, 220, 230, 240];
   const hue = waterHues[Math.floor(Math.random() * waterHues.length)];
   return `hsl(${hue}, 60%, 80%)`;
 }
-
-function getQueueStyle(item: QueueItem) {
+function getQueueStyle(item: any) {
   const start = getTimeString(item.startTime);
   const end = getTimeString(item.finishTime);
   const startDecimal = timeToDecimal(start);
   const endDecimal = timeToDecimal(end);
-  const totalHours = hours.value.length; // นับจำนวนชั่วโมงที่แสดง
-
-  // คำนวณอัตราส่วนของตำแหน่งเริ่มต้นและสิ้นสุด ตามจำนวนชั่วโมงทั้งหมด
+  const totalHours = hours.value.length;
   const leftPercent = ((startDecimal - timeToDecimal(startTime.value)) / totalHours) * 100;
   const widthPercent = ((endDecimal - startDecimal) / totalHours) * 100;
-
-  const backgroundColor = getQueueColor(item.orderID); // ใช้ฟังก์ชันที่แก้ไขแล้ว
-
-  console.log(`Queue ${item.queueID}: orderID=${item.orderID}, color=${backgroundColor}`);
-
+  const orderID = item.orderID || "Unknown";
+  const backgroundColor = getQueueColor(orderID);
+  console.log(`Queue ${item.QueueID}: orderID=${orderID}, color=${backgroundColor}`);
   return {
     left: `${leftPercent}%`,
     width: `${widthPercent}%`,
-    backgroundColor: backgroundColor, // ใช้สีแดงพาสเทลหรือสีธีมน้ำ
+    backgroundColor,
     color: '#333',
     borderRadius: '10px',
     textAlign: 'center',
@@ -327,51 +308,39 @@ function getQueueStyle(item: QueueItem) {
     boxShadow: '0px 4px 6px rgba(0, 0, 0, 0.1)',
   };
 }
-
-
-
-
 function getLineStyle(hour: number) {
-  const totalHours = hours.value.length; // นับจำนวนชั่วโมงที่แสดง (เช่น 10 ช่อง)
-  const leftPercent = (hour - timeToDecimal(startTime.value)) / totalHours * 100;
-  
+  const totalHours = hours.value.length;
+  const leftPercent = ((hour - timeToDecimal(startTime.value)) / totalHours) * 100;
   return { left: `${leftPercent}%` };
 }
-
 function timeToDecimal(timeStr: string): number {
-  const [hours, minutes] = timeStr.split(':').map(Number);
-  return hours + minutes / 60;
+  const [h, m] = timeStr.split(':').map(Number);
+  return h + m / 60;
 }
-
 function decimalToTime(decimal: number): string {
-  const hours = Math.floor(decimal);
-  const minutes = Math.round((decimal - hours) * 60);
-  return (hours < 10 ? '0' + hours : hours) + ':' + (minutes < 10 ? '0' + minutes : minutes);
+  const hrs = Math.floor(decimal);
+  const minutes = Math.round((decimal - hrs) * 60);
+  return (hrs < 10 ? '0' + hrs : hrs) + ':' + (minutes < 10 ? '0' + minutes : minutes);
 }
 
 // Drag/Drop & Resize Functions
-function onDragStart(event: DragEvent, item: QueueItem) {
+function onDragStart(event: DragEvent, item: any) {
   draggingQueue.value = item;
   const target = event.target as HTMLElement;
   const rect = target.getBoundingClientRect();
   dragOffset.value = rect.width / 2;
-
   const emptyImg = new Image();
   emptyImg.src = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=';
   event.dataTransfer?.setDragImage(emptyImg, 0, 0);
-
   ghostQueue.value = { ...item };
   ghostStyle.display = 'block';
-  ghostStyle.backgroundColor = getQueueColor(item.orderID);
-
+  ghostStyle.backgroundColor = getQueueColor(item.order?.OrderID || "Unknown");
   document.addEventListener('dragover', onDragOverGlobal);
   document.addEventListener('dragend', onDragEndGlobal);
 }
-
 function onDragOver(event: DragEvent) {
   event.preventDefault();
 }
-
 function onDragOverGlobal(event: DragEvent) {
   event.preventDefault();
   const rowTimeline = document.querySelector('.row-timeline') as HTMLElement;
@@ -389,7 +358,6 @@ function onDragOverGlobal(event: DragEvent) {
   const startDecimal = timeToDecimal(getTimeString(draggingQueue.value.startTime));
   const endDecimal = timeToDecimal(getTimeString(draggingQueue.value.finishTime));
   const duration = endDecimal - startDecimal;
-
   let newStartDecimal = timelineStartDec + ratio * (timelineEndDec - timelineStartDec);
   newStartDecimal = Math.round(newStartDecimal * 2) / 2;
   let newEndDecimal = newStartDecimal + duration;
@@ -407,11 +375,13 @@ function onDragOverGlobal(event: DragEvent) {
     ghostQueue.value.finishTime = newEndStr;
   }
 
+  // คำนวณตำแหน่ง ghost element
   const snappedRatioStart = (newStartDecimal - timelineStartDec) / (timelineEndDec - timelineStartDec);
   const snappedLeft = timelineRect.left + snappedRatioStart * timelineWidth;
   const snappedRatioEnd = (newEndDecimal - timelineStartDec) / (timelineEndDec - timelineStartDec);
   const snappedWidth = (snappedRatioEnd - snappedRatioStart) * timelineWidth;
 
+  // หาจุด row ที่ใกล้เคียงที่สุดและดึง machine id จาก attribute
   const rows = document.querySelectorAll('.row');
   let closestRow: HTMLElement | null = null;
   let minDistance = Infinity;
@@ -425,9 +395,10 @@ function onDragOverGlobal(event: DragEvent) {
   });
 
   if (closestRow) {
-    const labelEl = closestRow.querySelector('.machine-label') as HTMLElement;
-    if (labelEl) {
-      ghostQueue.value!.machineID = labelEl.textContent?.trim() || ghostQueue.value!.machineID;
+    // ใช้ dataset เพื่อดึง machine id
+    const machineIDString = closestRow.dataset.machineId;
+    if (machineIDString) {
+      ghostQueue.value.machine = { MachineID: parseInt(machineIDString, 10) };
     }
     ghostStyle.top = closestRow.getBoundingClientRect().top + 'px';
   }
@@ -442,38 +413,45 @@ function onDragEndGlobal() {
   draggingQueue.value = null;
   document.removeEventListener('dragend', onDragEndGlobal);
 }
-
-function onDragEnd(event: DragEvent, item: QueueItem) {
+async function onDragEnd(event: DragEvent, item: any) {
   if (!ghostQueue.value) return;
+  console.log(`🔄 Updating Queue ${item.QueueID} after drag...`);
   item.startTime = ghostQueue.value.startTime;
   item.finishTime = ghostQueue.value.finishTime;
-  item.machineID = ghostQueue.value.machineID;
-  document.removeEventListener('dragover', onDragOverGlobal);
+  // ใช้ ghostQueue.value.machine.MachineID แทนการอ่าน machineID จาก ghostQueue.value
+  const newMachineID = ghostQueue.value.machine?.MachineID || item.machineID;
+  await queueStore.updateQueue(item.QueueID, {
+    startTime: item.startTime,
+    finishTime: item.finishTime,
+    machine: { MachineID: newMachineID, name: '' }
+  });
   ghostQueue.value = null;
   ghostStyle.display = 'none';
   draggingQueue.value = null;
 }
-
-function onDrop(event: DragEvent, newMachine: string) {
+async function onDrop(event: DragEvent, newMachineID: number) {
   event.preventDefault();
   if (draggingQueue.value && ghostQueue.value) {
     draggingQueue.value.startTime = ghostQueue.value.startTime;
     draggingQueue.value.finishTime = ghostQueue.value.finishTime;
-    draggingQueue.value.machineID = newMachine;
+    draggingQueue.value.machine = { MachineID: newMachineID };
+    await queueStore.updateQueue(draggingQueue.value.QueueID, {
+      startTime: draggingQueue.value.startTime,
+      finishTime: draggingQueue.value.finishTime,
+      machine: { MachineID: newMachineID, name: '' }
+    });
   }
   ghostQueue.value = null;
   ghostStyle.display = 'none';
   draggingQueue.value = null;
 }
-
-function onResizeStart(event: MouseEvent, item: QueueItem, direction: string) {
+function onResizeStart(event: MouseEvent, item: any, direction: string) {
   event.preventDefault();
   resizingQueue.value = item;
   resizeDirection.value = direction;
   document.addEventListener('mousemove', onResizing);
   document.addEventListener('mouseup', onResizeEnd);
 }
-
 function onResizing(event: MouseEvent) {
   if (!resizingQueue.value) return;
   const rowTimeline = document.querySelector('.row-timeline') as HTMLElement;
@@ -481,10 +459,8 @@ function onResizing(event: MouseEvent) {
   const timelineRect = rowTimeline.getBoundingClientRect();
   const timelineWidth = timelineRect.width;
   const offsetX = event.clientX - timelineRect.left;
-
   let ratio = offsetX / timelineWidth;
   ratio = Math.min(Math.max(ratio, 0), 1);
-
   const timelineStartDec = timeToDecimal(startTime.value);
   const timelineEndDec = timeToDecimal(endTime.value);
   const startDec = timeToDecimal(getTimeString(resizingQueue.value.startTime));
@@ -492,25 +468,41 @@ function onResizing(event: MouseEvent) {
   let newTimeDecimal = timelineStartDec + ratio * (timelineEndDec - timelineStartDec);
   newTimeDecimal = Math.round(newTimeDecimal * 2) / 2;
   const datePart = getDateString(resizingQueue.value.startTime);
-
   if (resizeDirection.value === 'left') {
     if (newTimeDecimal >= endDec) return;
     const newStartStr = datePart + ' ' + decimalToTime(newTimeDecimal);
-    resizingQueue.value.startTime = newStartStr;
+    // อัปเดต ghostQueue ให้แสดงผลแบบ real-time
+    ghostQueue.value.startTime = newStartStr;
   } else if (resizeDirection.value === 'right') {
     if (newTimeDecimal <= startDec) return;
     const newEndStr = datePart + ' ' + decimalToTime(newTimeDecimal);
-    resizingQueue.value.finishTime = newEndStr;
+    ghostQueue.value.finishTime = newEndStr;
   }
+  // อัปเดต ghostStyle ตาม ghostQueue ใหม่
+  const start = getTimeString(ghostQueue.value.startTime);
+  const end = getTimeString(ghostQueue.value.finishTime);
+  const startDecimal = timeToDecimal(start);
+  const endDecimal = timeToDecimal(end);
+  const totalHours = hours.value.length;
+  const leftPercent = ((startDecimal - timeToDecimal(startTime.value)) / totalHours) * 100;
+  const widthPercent = ((endDecimal - startDecimal) / totalHours) * 100;
+  ghostStyle.left = `${leftPercent}%`;
+  ghostStyle.width = `${widthPercent}%`;
 }
-
-function onResizeEnd() {
+async function onResizeEnd() {
+  if (!resizingQueue.value) return;
+  console.log(`🔄 Updating Queue ${resizingQueue.value.QueueID} after resize...`);
+  await queueStore.updateQueue(resizingQueue.value.QueueID, {
+    startTime: ghostQueue.value.startTime,
+    finishTime: ghostQueue.value.finishTime
+  });
   resizingQueue.value = null;
   resizeDirection.value = null;
+  ghostQueue.value = null;
+  ghostStyle.display = 'none';
   document.removeEventListener('mousemove', onResizing);
   document.removeEventListener('mouseup', onResizeEnd);
 }
-
 // Pagination Functions
 function addPage() {
   if (pages.value.length < 10) {
@@ -520,12 +512,10 @@ function addPage() {
     alert("Maximum of 10 pages allowed.");
   }
 }
-
 function onPageRightClick(page: number, event: MouseEvent) {
   event.preventDefault();
   pageToShowDelete.value = page;
 }
-
 function deletePage(page: number) {
   const index = pages.value.indexOf(page);
   if (index !== -1) {
@@ -536,19 +526,15 @@ function deletePage(page: number) {
   }
   pageToShowDelete.value = null;
 }
-
 function onDocumentClick(event: MouseEvent) {
   const target = event.target as HTMLElement;
   if (!target.closest('.page-btn')) {
     pageToShowDelete.value = null;
   }
 }
-
-// Lifecycle Hooks
 onMounted(() => {
   document.addEventListener('click', onDocumentClick);
 });
-
 onBeforeUnmount(() => {
   document.removeEventListener('click', onDocumentClick);
 });
-- 
GitLab