diff --git a/src/components/GanttChart.vue b/src/components/GanttChart/GanttChart.vue similarity index 55% rename from src/components/GanttChart.vue rename to src/components/GanttChart/GanttChart.vue index 22de6277cd56653ded559f75f531b44579ed89c3..4da3f5e26150a7c1ad81133d4587db0b0c0833eb 100644 --- a/src/components/GanttChart.vue +++ b/src/components/GanttChart/GanttChart.vue @@ -12,32 +12,36 @@ <!-- Rows: เครื่องจักรแต่ละตัว --> <div class="rows"> - <div v-for="machine in machines" :key="machine.id" class="row" @dragover.prevent="onDragOver($event)" - @drop="onDrop($event, machine.name)"> + <div + v-for="machine in machines" + :key="machine.id" + class="row" + @dragover.prevent="onDragOver" + @drop="onDrop($event, machine.name)" + > <div class="machine-label"> {{ 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> - - <!-- แสดง Order --> <div + v-for="hour in hours" + :key="'line-' + hour" + class="vertical-line" + :style="getLineStyle(hour)" + ></div> + + <!-- แสดง Order โดยใช้ OrderItem Component --> + <OrderItem v-for="order in orders.filter(o => pages.includes(o.page) && o.page === currentPage && o.machine === machine.name)" - :key="order.id" class="order" :class="{ 'faded': draggingOrder && draggingOrder.id === order.id }" - :style="getOrderStyle(order)"> - <!-- Handle สำหรับ Resize ด้านซ้าย --> - <div class="resize-handle left" @mousedown="onResizeStart($event, order, 'left')"></div> - - <!-- ส่วนกลางของ Order ใช้สำหรับลาก --> - <div class="order-content" draggable="true" @dragstart="onDragStart($event, order)" - @dragend="onDragEnd($event, order)"> - {{ order.name }} ({{ order.start }} - {{ order.end }}) - </div> - - <!-- Handle สำหรับ Resize ด้านขวา --> - <div class="resize-handle right" @mousedown="onResizeStart($event, order, 'right')"></div> - </div> + :key="order.id" + :order="order" + :draggingOrder="draggingOrder" + :getOrderStyle="getOrderStyle" + @resizeStart="onResizeStart" + @dragStart="onDragStart" + @dragEnd="onDragEnd" + /> </div> </div> </div> @@ -49,20 +53,27 @@ <v-divider :thickness="7"></v-divider> - <!-- Pagination --> - <div class="pagination"> - <button v-for="p in pages" :key="p" :class="['page-btn', { active: p === currentPage }]" @click="currentPage = p"> - {{ p }} - </button> - <!-- ปุ่ม + สำหรับเพิ่มหน้าใหม่ --> - <button class="page-btn add-page" @click="addPage">+</button> - </div> + <!-- Pagination Component --> + <GanttPagination + :pages="pages" + :currentPage="currentPage" + @updateCurrentPage="updateCurrentPage" + @addPage="addPage" + @deletePage="deletePage" + /> </div> </template> <script> +import OrderItem from './OrderItem.vue'; +import GanttPagination from './GanttPagination.vue'; + export default { name: 'GanttChart', + components: { + OrderItem, + GanttPagination + }, data() { return { // เวลางาน @@ -81,7 +92,7 @@ export default { // รายการ Order orders: [ { page: 1, id: 1, name: 'Order 1', start: '13:00', end: '15:00', machine: 'MC1', color: 'blue' }, - { page: 1, id: 2, name: 'Order 2', start: '09:00', end: '11:00', machine: 'MC2' }, + { page: 1, id: 2, name: 'Order 2', start: '09:00', end: '11:00', machine: 'MC2', color: 'red' }, { page: 2, id: 3, name: 'Order 3', start: '10:00', end: '12:00', machine: 'MC1' }, { page: 2, id: 4, name: 'Order 4', start: '14:00', end: '16:00', machine: 'MC5' }, ], @@ -92,8 +103,8 @@ export default { resizingOrder: null, resizeDirection: null, - // Ghost Order (ตัวอย่างที่ขยับตามเม้าส์) - ghostOrder: null, + // Ghost Order + ghostOrder: null, ghostStyle: { position: 'fixed', top: '0px', @@ -110,7 +121,7 @@ export default { zIndex: 9999, }, - // จัดการ Pagination + // Pagination Data pages: [1, 2], currentPage: 1, } @@ -124,91 +135,62 @@ export default { } }, methods: { - // แปลงเลขชั่วโมงเป็น "HH:00" formatHour(hour) { return (hour < 10 ? '0' + hour : hour) + ':00'; }, - - // สไตล์การวาง Order (ตำแหน่ง left, width, และ backgroundColor) getOrderStyle(order) { const startDecimal = this.timeToDecimal(order.start); const endDecimal = this.timeToDecimal(order.end); const timelineStart = this.timeToDecimal(this.startTime); const timelineEnd = this.timeToDecimal(this.endTime); - const ratioStart = (startDecimal - timelineStart) / (timelineEnd - timelineStart); const ratioEnd = (endDecimal - timelineStart) / (timelineEnd - timelineStart); - - const left = ratioStart * 100 + '%'; - const width = (ratioEnd - ratioStart) * 100 + '%'; - return { - left, - width, + left: ratioStart * 100 + '%', + width: (ratioEnd - ratioStart) * 100 + '%', backgroundColor: order.color || '#4caf50' }; }, - - // สไตล์ของเส้นแนวตั้ง (grid lines) ตามชั่วโมง getLineStyle(hour) { - const startDecimal = this.timeToDecimal(this.startTime); - const endDecimal = this.timeToDecimal(this.endTime); - const ratio = (hour - startDecimal) / (endDecimal - startDecimal); + const timelineStart = this.timeToDecimal(this.startTime); + const timelineEnd = this.timeToDecimal(this.endTime); + const ratio = (hour - timelineStart) / (timelineEnd - timelineStart); return { left: ratio * 100 + '%' }; }, - - // แปลงเวลา "HH:MM" -> เลขทศนิยม (เช่น 9:30 => 9.5) timeToDecimal(timeStr) { const [hours, minutes] = timeStr.split(':').map(Number); return hours + minutes / 60; }, - - // แปลงเลขทศนิยม -> "HH:MM" decimalToTime(decimal) { const hours = Math.floor(decimal); const minutes = Math.round((decimal - hours) * 60); return (hours < 10 ? '0' + hours : hours) + ':' + (minutes < 10 ? '0' + minutes : minutes); }, onDragEndGlobal() { - // ลบ Ghost Order ออกจาก UI this.ghostOrder = null; this.ghostStyle.display = 'none'; this.draggingOrder = null; - - // เอา event listener ออก (ไม่งั้นมันจะถูกเรียกทุกครั้งที่ drag) document.removeEventListener('dragend', this.onDragEndGlobal); - }, - - // เริ่มลาก Order (DragStart) + }, onDragStart(event, order) { this.draggingOrder = order; const rect = event.target.getBoundingClientRect(); - this.dragOffset = event.target.getBoundingClientRect().width / 2; - - // ปิด preview เริ่มต้นของ HTML5 Drag & Drop (หรือใส่เป็นภาพโปร่งใสก็ได้) + this.dragOffset = rect.width / 2; + // ปิด preview ของ HTML5 Drag & Drop const emptyImg = document.createElement('img'); emptyImg.src = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='; event.dataTransfer.setDragImage(emptyImg, 0, 0); - - // สร้าง Ghost Order - this.ghostOrder = { ...order }; // clone ข้อมูลของ Order + this.ghostOrder = { ...order }; this.ghostStyle.display = 'block'; this.ghostStyle.backgroundColor = order.color || '#4caf50'; - - // ฟัง event dragover ทั้งหน้า (หรือจะใช้ใน .rows ก็ได้) document.addEventListener('dragover', this.onDragOverGlobal); document.addEventListener('dragend', this.onDragEndGlobal); }, - - // ขณะลาก (DragOver) ที่ผูกใน template (กัน default เพื่อให้ drop ได้) onDragOver(event) { - // ไม่ได้ทำอะไรมากในที่นี้ เพราะเราจัดการใน onDragOverGlobal event.preventDefault(); }, - - // onDragOverGlobal - อัปเดตตำแหน่ง Ghost และเวลา onDragOverGlobal(event) { event.preventDefault(); const rowTimeline = document.querySelector('.row-timeline'); @@ -216,43 +198,28 @@ export default { const timelineRect = rowTimeline.getBoundingClientRect(); const timelineWidth = timelineRect.width; const offsetX = event.clientX - timelineRect.left; - - // 1️⃣ คำนวณ Snap ตามแนวนอน (เวลา) let ratio = offsetX / timelineWidth; ratio = Math.min(Math.max(ratio, 0), 1); - const timelineStart = this.timeToDecimal(this.startTime); const timelineEnd = this.timeToDecimal(this.endTime); let newStartDecimal = timelineStart + ratio * (timelineEnd - timelineStart); - - // ปัดเป็นช่วงครึ่งชั่วโมง (30 นาที) newStartDecimal = Math.round(newStartDecimal * 2) / 2; - - // คำนวณช่วงเวลาของ Order const duration = this.timeToDecimal(this.draggingOrder.end) - this.timeToDecimal(this.draggingOrder.start); let newEndDecimal = newStartDecimal + duration; if (newEndDecimal > timelineEnd) { newEndDecimal = timelineEnd; newStartDecimal = newEndDecimal - duration; } - - // อัปเดตเวลาใน ghostOrder this.ghostOrder.start = this.decimalToTime(newStartDecimal); this.ghostOrder.end = this.decimalToTime(newEndDecimal); - - // คำนวณตำแหน่ง snapped left ตาม grid const snappedRatioStart = (newStartDecimal - timelineStart) / (timelineEnd - timelineStart); const snappedLeft = timelineRect.left + snappedRatioStart * timelineWidth; - - // คำนวณความกว้างของ Ghost Order const snappedRatioEnd = (newEndDecimal - timelineStart) / (timelineEnd - timelineStart); const snappedWidth = (snappedRatioEnd - snappedRatioStart) * timelineWidth; - - // 2️⃣ คำนวณ Snap ตามแนวตั้ง (เครื่องจักร) - const rows = document.querySelectorAll('.row'); // ดึงทุกแถว + // คำนวณ snap ตามแนวตั้ง (เครื่องจักร) + const rows = document.querySelectorAll('.row'); let closestRow = null; let minDistance = Infinity; - rows.forEach(row => { const rect = row.getBoundingClientRect(); const distance = Math.abs(event.clientY - rect.top); @@ -261,54 +228,34 @@ export default { closestRow = row; } }); - if (closestRow) { - this.ghostOrder.machine = closestRow.querySelector('.machine-label').textContent.trim(); // อัปเดตเครื่องจักรที่ snap - this.ghostStyle.top = closestRow.getBoundingClientRect().top + 'px'; // Snap ไปที่ตำแหน่งของ row นั้น + this.ghostOrder.machine = closestRow.querySelector('.machine-label').textContent.trim(); + this.ghostStyle.top = closestRow.getBoundingClientRect().top + 'px'; } - - // 3️⃣ อัปเดตตำแหน่งและขนาดของ Ghost Order this.ghostStyle.left = snappedLeft + 'px'; this.ghostStyle.width = snappedWidth + 'px'; }, - - - // จบการลาก Order (DragEnd) => คำนวณตำแหน่งใหม่ onDragEnd(event, order) { - if (!this.ghostOrder) return; - - // ใช้ค่าที่ snap แล้วจาก Ghost Order - order.start = this.ghostOrder.start; - order.end = this.ghostOrder.end; - order.machine = this.ghostOrder.machine; - - // ลบ Ghost Order - document.removeEventListener('dragover', this.onDragOverGlobal); - this.ghostOrder = null; - this.ghostStyle.display = 'none'; - this.draggingOrder = null; -} -, - - // เมื่อปล่อย Drag บนเครื่องจักรใหม่ => เปลี่ยน machine ของ Order + if (!this.ghostOrder) return; + order.start = this.ghostOrder.start; + order.end = this.ghostOrder.end; + order.machine = this.ghostOrder.machine; + document.removeEventListener('dragover', this.onDragOverGlobal); + this.ghostOrder = null; + this.ghostStyle.display = 'none'; + this.draggingOrder = null; + }, onDrop(event, newMachine) { - event.preventDefault(); - - if (this.draggingOrder) { - // ใช้ค่าที่ snap แล้วจาก Ghost Order - this.draggingOrder.start = this.ghostOrder.start; - this.draggingOrder.end = this.ghostOrder.end; - this.draggingOrder.machine = newMachine; // ใช้ค่าของเครื่องจักรที่ปล่อยลงไป - } - - // ลบ Ghost Order - this.ghostOrder = null; - this.ghostStyle.display = 'none'; - this.draggingOrder = null; -} -, - - // เริ่ม Resize (mousedown ที่ handle) + event.preventDefault(); + if (this.draggingOrder) { + this.draggingOrder.start = this.ghostOrder.start; + this.draggingOrder.end = this.ghostOrder.end; + this.draggingOrder.machine = newMachine; + } + this.ghostOrder = null; + this.ghostStyle.display = 'none'; + this.draggingOrder = null; + }, onResizeStart(event, order, direction) { event.preventDefault(); this.resizingOrder = order; @@ -316,56 +263,53 @@ export default { document.addEventListener("mousemove", this.onResizing); document.addEventListener("mouseup", this.onResizeEnd); }, - - // ขณะ Resize onResizing(event) { if (!this.resizingOrder) return; - const rowTimeline = document.querySelector('.row-timeline'); if (!rowTimeline) return; - 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 timelineStart = this.timeToDecimal(this.startTime); const timelineEnd = this.timeToDecimal(this.endTime); let newTimeDecimal = timelineStart + ratio * (timelineEnd - timelineStart); - - // ปัดเป็นช่วงครึ่งชั่วโมง newTimeDecimal = Math.round(newTimeDecimal * 2) / 2; - if (this.resizeDirection === 'left') { - // ไม่ให้ start เกิน end if (newTimeDecimal >= this.timeToDecimal(this.resizingOrder.end)) return; this.resizingOrder.start = this.decimalToTime(newTimeDecimal); } else if (this.resizeDirection === 'right') { - // ไม่ให้ end น้อยกว่า start if (newTimeDecimal <= this.timeToDecimal(this.resizingOrder.start)) return; this.resizingOrder.end = this.decimalToTime(newTimeDecimal); } }, - - // จบการ Resize onResizeEnd() { this.resizingOrder = null; this.resizeDirection = null; document.removeEventListener("mousemove", this.onResizing); document.removeEventListener("mouseup", this.onResizeEnd); }, - - // เพิ่มหน้าใหม่ addPage() { const newPage = this.pages.length + 1; this.pages.push(newPage); + }, + updateCurrentPage(page) { + this.currentPage = page; + }, + deletePage(page) { + const index = this.pages.indexOf(page); + if (index !== -1) { + this.pages.splice(index, 1); + if (this.currentPage === page) { + this.currentPage = this.pages.length > 0 ? this.pages[0] : 1; + } + } } } } </script> - <style scoped> .gantt-chart { width: 100%; @@ -411,7 +355,6 @@ export default { display: flex; min-height: 60px; border-bottom: 1px solid #ddd; - /* เส้นแบ่งระหว่างเครื่องจักร (แนวนอน) */ } .row-timeline { @@ -428,86 +371,9 @@ export default { background-color: #ddd; } -.order { - position: absolute; - top: 10px; - height: 40px; - color: #fff; - border-radius: 20px; - user-select: none; - display: flex; - align-items: center; - z-index: 1; -} - -.order-content { - flex: 1; - text-align: center; - line-height: 40px; - cursor: grab; - padding: 0 10px; -} - -.resize-handle { - width: 5px; - height: 100%; - cursor: ew-resize; - position: absolute; -} - -.resize-handle.left { - left: 0; - border-top-left-radius: 10px; - border-bottom-left-radius: 10px; -} - -.resize-handle.right { - right: 0; - border-top-right-radius: 10px; - border-bottom-right-radius: 10px; -} - -/* เมื่อกำลังลาก Order ตัวจริงให้จางลง */ -.order.faded { - opacity: 0.3; -} - -/* Ghost Order */ .drag-ghost { box-sizing: border-box; text-align: center; pointer-events: none; - /* กำหนดสไตล์เพิ่มเติมตามต้องการ */ -} - -.pagination { - display: flex; - justify-content: flex-start; - /* จัดเรียงชิดซ้าย */ - align-items: center; - /* จัดกึ่งกลางแนวตั้ง */ - gap: 5px; - /* เว้นระยะระหว่างปุ่ม */ - width: 100%; - /* ทำให้ `.pagination` ขยายเต็มพื้นที่ */ - margin-top: 10px; -} - -.page-btn { - background: #ffffff; - border: 1px solid #ffffff; - padding: 5px 10px; - cursor: pointer; - font-size: 14px; -} - -.page-btn.active { - background: #007bff; - color: #ffffff; -} - -.page-btn.add-page { - font-weight: bold; - background: #ffffff; } </style> diff --git a/src/components/GanttChart/GanttPagination.vue b/src/components/GanttChart/GanttPagination.vue new file mode 100644 index 0000000000000000000000000000000000000000..97f3b7d409ce4c06426b8946277bfc4cc6435717 --- /dev/null +++ b/src/components/GanttChart/GanttPagination.vue @@ -0,0 +1,107 @@ +<template> + <div class="pagination"> + <button + v-for="p in pages" + :key="p" + :class="['page-btn', { active: p === currentPage }]" + @click="$emit('updateCurrentPage', p)" + @contextmenu.prevent="onPageRightClick(p, $event)" + > + {{ p }} + <!-- แสดงปุ่ม Delete เมื่อคลิกขวาที่ปุ่มนั้น --> + <button + v-if="pageToShowDelete === p" + class="delete-btn" + @click.stop="deletePage(p)" + > + Delete + </button> + </button> + <!-- ปุ่ม + สำหรับเพิ่มหน้าใหม่ --> + <button class="page-btn add-page" @click="$emit('addPage')">+</button> + </div> + </template> + + <script> + export default { + name: 'GanttPagination', + props: { + pages: { + type: Array, + required: true + }, + currentPage: { + type: Number, + required: true + } + }, + data() { + return { + pageToShowDelete: null + } + }, + methods: { + onPageRightClick(page, event) { + event.preventDefault(); + this.pageToShowDelete = page; + }, + deletePage(page) { + this.$emit('deletePage', page); + this.pageToShowDelete = null; + }, + onDocumentClick(event) { + if (!event.target.closest('.page-btn')) { + this.pageToShowDelete = null; + } + } + }, + mounted() { + document.addEventListener('click', this.onDocumentClick); + }, + beforeUnmount() { + document.removeEventListener('click', this.onDocumentClick); + } + } + </script> + + <style scoped> + .pagination { + display: flex; + justify-content: flex-start; + align-items: center; + gap: 5px; + width: 100%; + margin-top: 10px; + } + + .page-btn { + background: #ffffff; + border: 1px solid #ffffff; + padding: 5px 10px; + cursor: pointer; + font-size: 14px; + position: relative; + } + + .page-btn.active { + background: #007bff; + color: #ffffff; + } + + .page-btn.add-page { + font-weight: bold; + background: #ffffff; + } + + .delete-btn { + margin-left: 5px; + background-color: red; + color: #fff; + border: none; + padding: 2px 5px; + cursor: pointer; + border-radius: 3px; + font-size: 12px; + } + </style> + \ No newline at end of file diff --git a/src/components/GanttChart/OrderItem.vue b/src/components/GanttChart/OrderItem.vue new file mode 100644 index 0000000000000000000000000000000000000000..45006536566dc3278c76cf2920b3614f02a4113a --- /dev/null +++ b/src/components/GanttChart/OrderItem.vue @@ -0,0 +1,101 @@ +<template> + <div + class="order" + :class="{ 'faded': draggingOrder && draggingOrder.id === order.id }" + :style="getOrderStyle(order)" + > + <!-- Handle สำหรับ Resize ด้านซ้าย --> + <div class="resize-handle left" @mousedown="handleResizeStart('left', $event)"></div> + + <!-- ส่วนกลางของ Order ใช้สำหรับลาก --> + <div + class="order-content" + draggable="true" + @dragstart="handleDragStart($event)" + @dragend="handleDragEnd($event)" + > + {{ order.name }} ({{ order.start }} - {{ order.end }}) + </div> + + <!-- Handle สำหรับ Resize ด้านขวา --> + <div class="resize-handle right" @mousedown="handleResizeStart('right', $event)"></div> + </div> + </template> + + <script> + export default { + name: 'OrderItem', + props: { + order: { + type: Object, + required: true + }, + draggingOrder: { + type: Object, + default: null + }, + getOrderStyle: { + type: Function, + required: true + } + }, + methods: { + handleResizeStart(direction, event) { + // ส่ง event พร้อม order และทิศทางไปยัง parent + this.$emit('resizeStart', event, this.order, direction); + }, + handleDragStart(event) { + this.$emit('dragStart', event, this.order); + }, + handleDragEnd(event) { + this.$emit('dragEnd', event, this.order); + } + } + } + </script> + + <style scoped> + .order { + position: absolute; + top: 10px; + height: 40px; + color: #fff; + border-radius: 20px; + user-select: none; + display: flex; + align-items: center; + z-index: 1; + } + + .order-content { + flex: 1; + text-align: center; + line-height: 40px; + cursor: grab; + padding: 0 10px; + } + + .resize-handle { + width: 5px; + height: 100%; + cursor: ew-resize; + position: absolute; + } + + .resize-handle.left { + left: 0; + border-top-left-radius: 10px; + border-bottom-left-radius: 10px; + } + + .resize-handle.right { + right: 0; + border-top-right-radius: 10px; + border-bottom-right-radius: 10px; + } + + .faded { + opacity: 0.3; + } + </style> + \ No newline at end of file diff --git a/src/components/OrderItem.vue b/src/components/OrderItem.vue new file mode 100644 index 0000000000000000000000000000000000000000..763e0fa7ca9757fedd5767833d070da8446edef7 --- /dev/null +++ b/src/components/OrderItem.vue @@ -0,0 +1,75 @@ +<template> + <div class="order" :style="getOrderStyle(order)"> + <div class="resize-handle left" @mousedown="$emit('resize-start', order, 'left')"></div> + <div class="order-content" draggable="true" @dragstart="$emit('drag-start', order)" @dragend="$emit('drag-end', order)"> + {{ order.name }} ({{ order.start }} - {{ order.end }}) + </div> + <div class="resize-handle right" @mousedown="$emit('resize-start', order, 'right')"></div> + </div> +</template> + +<script> +export default { + props: { + order: Object, + startTime: String, + endTime: String + }, + methods: { + timeToDecimal(timeStr) { + const [hours, minutes] = timeStr.split(':').map(Number); + return hours + minutes / 60; + }, + getOrderStyle(order) { + const startDecimal = this.timeToDecimal(order.start); + const endDecimal = this.timeToDecimal(order.end); + const timelineStart = this.timeToDecimal(this.startTime); + const timelineEnd = this.timeToDecimal(this.endTime); + const ratioStart = (startDecimal - timelineStart) / (timelineEnd - timelineStart); + const ratioEnd = (endDecimal - timelineStart) / (timelineEnd - timelineStart); + return { + left: ratioStart * 100 + '%', + width: (ratioEnd - ratioStart) * 100 + '%', + backgroundColor: order.color || '#4caf50' + }; + } + } +}; +</script> + +<style scoped> +.order { + position: absolute; + top: 10px; + height: 40px; + color: #fff; + border-radius: 20px; + user-select: none; + display: flex; + align-items: center; + z-index: 1; +} + +.order-content { + flex: 1; + text-align: center; + line-height: 40px; + cursor: grab; + padding: 0 10px; +} + +.resize-handle { + width: 5px; + height: 100%; + cursor: ew-resize; + position: absolute; +} + +.resize-handle.left { + left: 0; +} + +.resize-handle.right { + right: 0; +} +</style> diff --git a/src/views/ProductQueueView.vue b/src/views/ProductQueueView.vue index 7d7c87b988dd14a53424d4adddad35b217c70990..b8802a7e9e598a2a517dff2285ebddba42a267c0 100644 --- a/src/views/ProductQueueView.vue +++ b/src/views/ProductQueueView.vue @@ -1,6 +1,6 @@ <script setup> import { ref } from 'vue' -import GanttChart from '@/components/GanttChart.vue' +import GanttChart from '@/components/GanttChart/GanttChart.vue' const selectedDate = ref('1/1/2024') const employees = Array.from({ length: 10 }, (_, i) => `EM${i + 1}`) const orders = ['Order1', 'Order2']