diff --git a/src/components/GanttChart/GanttChart.vue b/src/components/GanttChart/GanttChart.vue index de84e2a1b5b0984ba37dd383588a1c7e4e1f52ff..9be07e07b3125cf61d6b61cfc81ed4c09c2c80f2 100644 --- a/src/components/GanttChart/GanttChart.vue +++ b/src/components/GanttChart/GanttChart.vue @@ -40,35 +40,35 @@ :style="getLineStyle(hour)" ></div> - <!-- แสดง Order ที่ตรงกับวันที่ (selectedDate), Page, และ Machine --> + <!-- แสดง Queue ที่ตรงกับวันที่ (selectedDate), Page, และ Machine --> <div - v-for="order in filteredOrders(machine.machineID)" - :key="order.queueID" + v-for="item in filteredQueue(machine.machineID)" + :key="item.queueID" class="order" - :class="{ 'faded': draggingOrder && draggingOrder.queueID === order.queueID }" - :style="getOrderStyle(order)" + :class="{ 'faded': draggingQueue && draggingQueue.queueID === item.queueID }" + :style="getQueueStyle(item)" > <!-- Handle สำหรับ Resize ด้านซ้าย --> <div class="resize-handle left" - @mousedown="onResizeStart($event, order, 'left')" + @mousedown="onResizeStart($event, item, 'left')" ></div> <!-- ส่วนกลางของ Order ใช้สำหรับลาก และเปิด Dialog เมื่อคลิก --> <div class="order-content" draggable="true" - @dragstart="onDragStart($event, order)" - @dragend="onDragEnd($event, order)" - @click.stop="openOrderDialog(order)" + @dragstart="onDragStart($event, item)" + @dragend="onDragEnd($event, item)" + @click.stop="openQueueDialog(item)" > - {{ order.orderID }} ({{ getTimeString(order.startTime) }} - {{ getTimeString(order.finishTime) }}) + {{ item.orderID }} ({{ getTimeString(item.startTime) }} - {{ getTimeString(item.finishTime) }}) </div> <!-- Handle สำหรับ Resize ด้านขวา --> <div class="resize-handle right" - @mousedown="onResizeStart($event, order, 'right')" + @mousedown="onResizeStart($event, item, 'right')" ></div> </div> </div> @@ -76,8 +76,9 @@ </div> <!-- Ghost Order ขณะลาก --> - <div v-if="ghostOrder" class="drag-ghost" :style="ghostStyle"> - {{ ghostOrder.orderID }} ({{ getTimeString(ghostOrder.startTime) }} - {{ getTimeString(ghostOrder.finishTime) }}) + <div v-if="ghostQueue" class="drag-ghost" :style="ghostStyle"> + {{ ghostQueue.orderID }} + ({{ getTimeString(ghostQueue.startTime) }} - {{ getTimeString(ghostQueue.finishTime) }}) </div> <v-divider :thickness="7"></v-divider> @@ -105,11 +106,12 @@ <!-- Order Dialog --> <OrderDialog - v-if="selectedOrder" - :order="selectedOrder" - @close="closeOrderDialog" - @edit="editOrder" - @delete="deleteOrder" + v-if="selectedQueueItem" + :queueItem="selectedQueueItem" + :color="getQueueColor(selectedQueueItem.orderID)" + @close="closeQueueDialog" + @edit="editQueueItem" + @delete="deleteQueueItem" /> </div> </div> @@ -144,12 +146,12 @@ export default { { id: 5, machineID: 'MC5' }, ], - // ข้อมูล Order ตามโครงสร้าง Queue - orders: [ + // เปลี่ยนจาก orders เป็น Queue + Queue: [ { queueID: 1, machineID: 'MC1', - orderID: 1, + orderID: 5, pageNumber: 1, startTime: '1/1/2024 09:00', finishTime: '1/1/2024 11:00', @@ -160,7 +162,7 @@ export default { { queueID: 2, machineID: 'MC2', - orderID: 2, + orderID: 1, pageNumber: 1, startTime: '1/1/2024 13:00', finishTime: '1/1/2024 15:00', @@ -171,7 +173,7 @@ export default { { queueID: 3, machineID: 'MC1', - orderID: 3, + orderID: 2, pageNumber: 2, startTime: '1/1/2024 10:00', finishTime: '1/1/2024 12:00', @@ -182,7 +184,7 @@ export default { { queueID: 4, machineID: 'MC5', - orderID: 4, + orderID: 2, pageNumber: 2, startTime: '1/1/2024 14:00', finishTime: '1/1/2024 16:00', @@ -204,13 +206,13 @@ export default { ], // จัดการการลาก, Resize - draggingOrder: null, + draggingQueue: null, dragOffset: 0, - resizingOrder: null, + resizingQueue: null, resizeDirection: null, // Ghost Order (ขณะลาก) - ghostOrder: null, + ghostQueue: null, ghostStyle: { position: 'fixed', top: '0px', @@ -233,7 +235,7 @@ export default { pageToShowDelete: null, // Dialog - selectedOrder: null, + selectedQueueItem: null, }; }, computed: { @@ -245,9 +247,12 @@ export default { }, }, methods: { + // แปลงเลขชั่วโมงเป็น "HH:00" + formatHour(hour) { + return (hour < 10 ? '0' + hour : hour) + ':00'; + }, // แยก date ส่วนหน้า (1/1/2024) ออกจาก startTime/finishTime getDateString(dateTimeStr) { - // สมมติ format = '1/1/2024 09:00' return dateTimeStr.split(' ')[0]; }, // แยกเฉพาะส่วนเวลา (09:00) @@ -255,45 +260,50 @@ export default { return dateTimeStr.split(' ')[1]; }, - // ฟิลเตอร์ order ตาม machineID, pageNumber, และ selectedDate - filteredOrders(machineID) { - return this.orders.filter((o) => { - const orderDate = this.getDateString(o.startTime); + // ฟิลเตอร์ Queue ตาม machineID, pageNumber, และ selectedDate + filteredQueue(machineID) { + return this.Queue.filter((q) => { + const queueDate = this.getDateString(q.startTime); return ( - o.machineID === machineID && - o.pageNumber === this.currentPage && - orderDate === this.selectedDate + q.machineID === machineID && + q.pageNumber === this.currentPage && + queueDate === this.selectedDate ); }); }, - // แปลงเลขชั่วโมงเป็น "HH:00" - formatHour(hour) { - return (hour < 10 ? '0' + hour : hour) + ':00'; + // ฟังก์ชันเลือกสีจาก orderID + getQueueColor(orderID) { + const colors = ['#F58181', '#FDDC5C', '#C5A1BC', '#49E060']; // 4 สีที่กำหนด + return colors[(orderID - 1) % colors.length]; // ใช้ modulo (%) เพื่อให้วนกลับไปที่สีแรกเมื่อเกิน 4 }, - // สไตล์ตำแหน่งของ Order - getOrderStyle(order) { - // แยกเวลาเฉพาะส่วน HH:mm - const start = this.getTimeString(order.startTime); // ex "09:00" - const end = this.getTimeString(order.finishTime); // ex "11:00" - - const startDecimal = this.timeToDecimal(start); - const endDecimal = this.timeToDecimal(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); + // สไตล์ตำแหน่ง + สีของ Queue + getQueueStyle(item) { + const start = this.getTimeString(item.startTime); + const end = this.getTimeString(item.finishTime); + const startDecimal = this.timeToDecimal(start); + const endDecimal = this.timeToDecimal(end); + const timelineStart = this.timeToDecimal(this.startTime); + const timelineEnd = this.timeToDecimal(this.endTime); - return { - left: ratioStart * 100 + '%', - width: (ratioEnd - ratioStart) * 100 + '%', - backgroundColor: '#4caf50', // สามารถปรับตาม status หรืออื่นๆ ได้ - }; - }, + const ratioStart = (startDecimal - timelineStart) / (timelineEnd - timelineStart); + const ratioEnd = (endDecimal - timelineStart) / (timelineEnd - timelineStart); - // สไตล์ของเส้นแนวตั้ง (grid lines) + return { + left: ratioStart * 100 + '%', // คำนวณตำแหน่งซ้าย + width: (ratioEnd - ratioStart) * 100 + '%', // คำนวณความกว้าง + backgroundColor: this.getQueueColor(item.orderID), // ใช้สีตาม orderID + color: '#fff', // ข้อความสีขาวให้อ่านง่าย + borderRadius: '10px', // ขอบโค้งมนให้ดูสวยขึ้น + textAlign: 'center', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + padding: '5px', + fontSize: '14px', + }; + }, getLineStyle(hour) { const timelineStart = this.timeToDecimal(this.startTime); const timelineEnd = this.timeToDecimal(this.endTime); @@ -302,22 +312,19 @@ export default { left: ratio * 100 + '%', }; }, - - // แปลงเวลา "HH:MM" -> เลขทศนิยม 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); }, - // Event ลาก (DragStart) - onDragStart(event, order) { - this.draggingOrder = order; + // ==================== Drag/Drop ==================== + onDragStart(event, item) { + this.draggingQueue = item; const rect = event.target.getBoundingClientRect(); this.dragOffset = rect.width / 2; // ปิด preview เริ่มต้นของ HTML5 Drag & Drop @@ -325,9 +332,10 @@ export default { emptyImg.src = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='; event.dataTransfer.setDragImage(emptyImg, 0, 0); - this.ghostOrder = { ...order }; + this.ghostQueue = { ...item }; this.ghostStyle.display = 'block'; - this.ghostStyle.backgroundColor = '#4caf50'; + // ใช้สีเดียวกับ orderID + this.ghostStyle.backgroundColor = this.getQueueColor(item.orderID); document.addEventListener('dragover', this.onDragOverGlobal); document.addEventListener('dragend', this.onDragEndGlobal); @@ -350,8 +358,8 @@ export default { const timelineEnd = this.timeToDecimal(this.endTime); // คำนวณเวลาเริ่มใหม่ (เฉพาะส่วนเวลา) - const startDecimal = this.timeToDecimal(this.getTimeString(this.draggingOrder.startTime)); - const endDecimal = this.timeToDecimal(this.getTimeString(this.draggingOrder.finishTime)); + const startDecimal = this.timeToDecimal(this.getTimeString(this.draggingQueue.startTime)); + const endDecimal = this.timeToDecimal(this.getTimeString(this.draggingQueue.finishTime)); const duration = endDecimal - startDecimal; let newStartDecimal = timelineStart + ratio * (timelineEnd - timelineStart); @@ -364,12 +372,12 @@ export default { } // สร้าง String ใหม่ "วัน/เดือน/ปี เวลา" - const datePart = this.getDateString(this.draggingOrder.startTime); // ex "1/1/2024" + const datePart = this.getDateString(this.draggingQueue.startTime); const newStartStr = datePart + ' ' + this.decimalToTime(newStartDecimal); const newEndStr = datePart + ' ' + this.decimalToTime(newEndDecimal); - this.ghostOrder.startTime = newStartStr; - this.ghostOrder.finishTime = newEndStr; + this.ghostQueue.startTime = newStartStr; + this.ghostQueue.finishTime = newEndStr; const snappedRatioStart = (newStartDecimal - timelineStart) / (timelineEnd - timelineStart); const snappedLeft = timelineRect.left + snappedRatioStart * timelineWidth; @@ -391,7 +399,7 @@ export default { }); if (closestRow) { - this.ghostOrder.machineID = closestRow.querySelector('.machine-label').textContent.trim(); + this.ghostQueue.machineID = closestRow.querySelector('.machine-label').textContent.trim(); this.ghostStyle.top = closestRow.getBoundingClientRect().top + 'px'; } @@ -399,43 +407,43 @@ export default { this.ghostStyle.width = snappedWidth + 'px'; }, onDragEndGlobal() { - this.ghostOrder = null; + this.ghostQueue = null; this.ghostStyle.display = 'none'; - this.draggingOrder = null; + this.draggingQueue = null; document.removeEventListener('dragend', this.onDragEndGlobal); }, - onDragEnd(event, order) { - if (!this.ghostOrder) return; - order.startTime = this.ghostOrder.startTime; - order.finishTime = this.ghostOrder.finishTime; - order.machineID = this.ghostOrder.machineID; + onDragEnd(event, item) { + if (!this.ghostQueue) return; + item.startTime = this.ghostQueue.startTime; + item.finishTime = this.ghostQueue.finishTime; + item.machineID = this.ghostQueue.machineID; document.removeEventListener('dragover', this.onDragOverGlobal); - this.ghostOrder = null; + this.ghostQueue = null; this.ghostStyle.display = 'none'; - this.draggingOrder = null; + this.draggingQueue = null; }, onDrop(event, newMachine) { event.preventDefault(); - if (this.draggingOrder && this.ghostOrder) { - this.draggingOrder.startTime = this.ghostOrder.startTime; - this.draggingOrder.finishTime = this.ghostOrder.finishTime; - this.draggingOrder.machineID = newMachine; + if (this.draggingQueue && this.ghostQueue) { + this.draggingQueue.startTime = this.ghostQueue.startTime; + this.draggingQueue.finishTime = this.ghostQueue.finishTime; + this.draggingQueue.machineID = newMachine; } - this.ghostOrder = null; + this.ghostQueue = null; this.ghostStyle.display = 'none'; - this.draggingOrder = null; + this.draggingQueue = null; }, - // Resize - onResizeStart(event, order, direction) { + // ==================== Resize ==================== + onResizeStart(event, item, direction) { event.preventDefault(); - this.resizingOrder = order; + this.resizingQueue = item; this.resizeDirection = direction; document.addEventListener('mousemove', this.onResizing); document.addEventListener('mouseup', this.onResizeEnd); }, onResizing(event) { - if (!this.resizingOrder) return; + if (!this.resizingQueue) return; const rowTimeline = document.querySelector('.row-timeline'); if (!rowTimeline) return; const timelineRect = rowTimeline.getBoundingClientRect(); @@ -448,32 +456,32 @@ export default { const timelineStart = this.timeToDecimal(this.startTime); const timelineEnd = this.timeToDecimal(this.endTime); - const startDec = this.timeToDecimal(this.getTimeString(this.resizingOrder.startTime)); - const endDec = this.timeToDecimal(this.getTimeString(this.resizingOrder.finishTime)); + const startDec = this.timeToDecimal(this.getTimeString(this.resizingQueue.startTime)); + const endDec = this.timeToDecimal(this.getTimeString(this.resizingQueue.finishTime)); let newTimeDecimal = timelineStart + ratio * (timelineEnd - timelineStart); newTimeDecimal = Math.round(newTimeDecimal * 2) / 2; - const datePart = this.getDateString(this.resizingOrder.startTime); // ex: "1/1/2024" + const datePart = this.getDateString(this.resizingQueue.startTime); // ex: "1/1/2024" if (this.resizeDirection === 'left') { if (newTimeDecimal >= endDec) return; const newStartStr = datePart + ' ' + this.decimalToTime(newTimeDecimal); - this.resizingOrder.startTime = newStartStr; + this.resizingQueue.startTime = newStartStr; } else if (this.resizeDirection === 'right') { if (newTimeDecimal <= startDec) return; const newEndStr = datePart + ' ' + this.decimalToTime(newTimeDecimal); - this.resizingOrder.finishTime = newEndStr; + this.resizingQueue.finishTime = newEndStr; } }, onResizeEnd() { - this.resizingOrder = null; + this.resizingQueue = null; this.resizeDirection = null; document.removeEventListener('mousemove', this.onResizing); document.removeEventListener('mouseup', this.onResizeEnd); }, - // Pagination + // ==================== Pagination ==================== addPage() { const newPage = this.pages.length + 1; this.pages.push(newPage); @@ -498,22 +506,22 @@ export default { } }, - // Dialog - openOrderDialog(order) { - this.selectedOrder = { ...order }; + // ==================== Dialog ==================== + openQueueDialog(item) { + this.selectedQueueItem = { ...item }; }, - closeOrderDialog() { - this.selectedOrder = null; + closeQueueDialog() { + this.selectedQueueItem = null; }, - editOrder(order) { - alert('Edit functionality not implemented yet.\nOrder ID: ' + order.orderID); + editQueueItem(item) { + alert('Edit functionality not implemented yet.\nOrder ID: ' + item.orderID); }, - deleteOrder(order) { - this.orders = this.orders.filter((o) => o.queueID !== order.queueID); - this.closeOrderDialog(); + deleteQueueItem(item) { + this.Queue = this.Queue.filter((q) => q.queueID !== item.queueID); + this.closeQueueDialog(); }, }, - mounted() { + mounted() { document.addEventListener('click', this.onDocumentClick); }, beforeUnmount() { diff --git a/src/components/GanttChart/OrderDialog.vue b/src/components/GanttChart/OrderDialog.vue index 81adfb371229788e2dab17d623ed49e72dd4d100..60e053b34c8116adeb44132711e62f5dd2e06c8e 100644 --- a/src/components/GanttChart/OrderDialog.vue +++ b/src/components/GanttChart/OrderDialog.vue @@ -1,35 +1,122 @@ <template> <div class="order-dialog-overlay"> - <div class="order-dialog"> - <h3>Order Details</h3> - <p><strong>QueueID:</strong> {{ order.queueID }}</p> - <p><strong>Machine:</strong> {{ order.machineID }}</p> - <p><strong>OrderID:</strong> {{ order.orderID }}</p> - <p><strong>PageNumber:</strong> {{ order.pageNumber }}</p> - <p><strong>Start:</strong> {{ order.startTime }}</p> - <p><strong>Finish:</strong> {{ order.finishTime }}</p> - <p><strong>Status:</strong> {{ order.status }}</p> - <p><strong>Bottle Size:</strong> {{ order.bottleSize }}</p> - <p><strong>Produced:</strong> {{ order.producedQuantity }}</p> - - <div class="dialog-buttons"> - <button @click="$emit('edit', order)">Edit</button> - <button @click="$emit('delete', order)">Delete</button> - <button @click="$emit('close')">Close</button> + <div class="order-dialog" :style="{ backgroundColor: color }"> + <div class="dialog-header"> + <h3 class="order-title">{{ queueItem.orderName }}</h3> + <div class="dialog-buttons"> + <button @click="$emit('edit', queueItem)" class="icon-button"> + <span class="mdi mdi-pencil"></span> + </button> + <button @click="$emit('delete', queueItem)" class="icon-button"> + <span class="mdi mdi-delete"></span> + </button> + <button @click="$emit('close')" class="icon-button"> + <span class="mdi mdi-close"></span> + </button> + </div> + </div> + <div class="order-details"> + <p><strong>เป่าขวดจำนวน</strong> {{ queueItem.producedQuantity }} ขวด</p> + <p><strong>ตั้งแต่เวลา</strong> {{ queueItem.startTime }} - {{ queueItem.finishTime }}</p> + </div> + <div class="order-assignments"> + <div class="machine"> + <span class="icon">🛠</span> {{ queueItem.machineID }} + </div> + <div class="employees" v-for="(employee, index) in queueItem.employees" :key="index"> + <span class="icon">👤</span> {{ employee }} + </div> + </div> </div> - </div> </div> - </template> - - <script> - export default { +</template> + +<script> +export default { name: 'OrderDialog', props: { - order: { - type: Object, - required: true, - }, + queueItem: { + type: Object, + required: true, + }, + color: { + type: String, + default: '#F28B82', // สีโทนแดงอ่อน + }, }, - }; - </script> - \ No newline at end of file +}; +</script> + +<style scoped> +.order-dialog-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.3); + display: flex; + align-items: center; + justify-content: center; + z-index: 10000; +} + +.order-dialog { + background: #F28B82; + padding: 15px; + border-radius: 12px; + width: 280px; + text-align: left; + position: relative; + box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); +} + +.dialog-header { + display: flex; + justify-content: space-between; + align-items: center; +} + +.order-title { + font-size: 1.2rem; + font-weight: bold; +} + +.dialog-buttons { + display: flex; + gap: 5px; +} + +.icon-button { + background: none; + border: none; + cursor: pointer; + font-size: 18px; +} + +.order-details { + background: white; + padding: 10px; + border-radius: 8px; + margin-top: 8px; + font-size: 0.9rem; +} + +.order-assignments { + margin-top: 10px; +} + +.machine, .employees { + background: white; + padding: 5px; + border-radius: 5px; + margin-top: 5px; + display: flex; + align-items: center; + gap: 5px; +} + +.icon { + font-size: 1rem; +} +</style>