diff --git a/src/components/GanttChart/GanttChart.vue b/src/components/GanttChart/GanttChart.vue index 392e37e717efbfe44e4a8f52e9112a1014ad85fd..30a66c5c322193431ad49ea2b5a0215491ba9fd3 100644 --- a/src/components/GanttChart/GanttChart.vue +++ b/src/components/GanttChart/GanttChart.vue @@ -25,28 +25,55 @@ <!-- Rows: แสดงเครื่องจักรจาก Machine Store --> <div class="rows"> - <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 + 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.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 (pagenum), และ 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> + <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)"> - {{ item.label }} ({{ getTimeString(item.startTime) }} - {{ getTimeString(item.finishTime) }}) + <div + class="order-content" + draggable="true" + @dragstart="onDragStart($event, item)" + @dragend="onDragEnd($event, item)" + @click.stop="openQueueDialog(item)" + > + {{ item.label }} ({{ getTimeString(item.startTime) }} - + {{ getTimeString(item.finishTime) }}) </div> <!-- Handle สำหรับ Resize ด้านขวา --> - <div class="resize-handle right" @mousedown="onResizeStart($event, item, 'right')"></div> + <div + class="resize-handle right" + @mousedown="onResizeStart($event, item, 'right')" + ></div> </div> </div> </div> @@ -54,7 +81,8 @@ <!-- Ghost Queue (ขณะลาก/resize) --> <div v-if="ghostQueue" class="drag-ghost" :style="ghostStyle"> - {{ ghostQueue.label }} ({{ getTimeString(ghostQueue.startTime) }} - {{ getTimeString(ghostQueue.finishTime) }}) + {{ ghostQueue.label }} ({{ getTimeString(ghostQueue.startTime) }} - + {{ getTimeString(ghostQueue.finishTime) }}) </div> <v-divider :thickness="7"></v-divider> @@ -62,8 +90,13 @@ <!-- Pagination --> <div class="pagination-container"> <transition-group name="fade" tag="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 @@ -78,71 +111,86 @@ <v-icon>mdi-plus</v-icon> </v-btn> </div> - <AddQueueDialog :visible="showAddDialog" :queueItem="selectedQueueItem" :currentPage="currentPage" - @close="closeAddQueueDialog" /> + <AddQueueDialog + :visible="showAddDialog" + :queueItem="selectedQueueItem" + :currentPage="currentPage" + @close="closeAddQueueDialog" + /> </div> - <OrderDialog v-if="selectedQueueItem && !showAddDialog" :queueId="selectedQueueItem.QueueID" - :color="getQueueColor(selectedQueueItem.QueueID)" @close="closeQueueDialog" @edit="openAddQueueDialogForEdit" - @delete="handleDelete" /> + <OrderDialog + v-if="selectedQueueItem && !showAddDialog" + :queueId="selectedQueueItem.QueueID" + :color=" + getQueueColor( + selectedQueueItem.label === 'ผลิตเผื่อ' || selectedQueueItem.label === 'เปลี่ยนขนาด' + ? selectedQueueItem.label + : selectedQueueItem.orderID + ) + " + @close="closeQueueDialog" + @edit="openAddQueueDialogForEdit" + @delete="handleDelete" + /> </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 { usePageStore } from '@/stores/page'; -import { usePageContextStore } from '@/stores/pageContext'; -import { useDateStore } from '@/stores/dateStore'; -import { storeToRefs } from 'pinia'; - -import GanttCalendar from './GanttCalendar.vue'; -import OrderDialog from './OrderDialog.vue'; -import AddQueueDialog from './AddQueueDialog.vue'; -import MakequeueBtn from './MakequeueBtn.vue'; -import SettingBtn from './SettingBtn.vue'; -import './ganttChart.css'; +import { ref, reactive, computed, onMounted, onBeforeUnmount, watch } from 'vue' +import { useQueueStore } from '@/stores/queue' +import { useMachineStore } from '@/stores/machine' +import { usePageStore } from '@/stores/page' +import { usePageContextStore } from '@/stores/pageContext' +import { useDateStore } from '@/stores/dateStore' +import { storeToRefs } from 'pinia' + +import GanttCalendar from './GanttCalendar.vue' +import OrderDialog from './OrderDialog.vue' +import AddQueueDialog from './AddQueueDialog.vue' +import MakequeueBtn from './MakequeueBtn.vue' +import SettingBtn from './SettingBtn.vue' +import './ganttChart.css' // Stores -const queueStore = useQueueStore(); -const machineStore = useMachineStore(); -const pageStore = usePageStore(); -const dateStore = useDateStore(); -const { currentDate: selectedDate } = storeToRefs(dateStore); -const pageContext = usePageContextStore(); - -const ORDER_COLORS = ['#FFCDD2', '#FFE0B2', '#90CAF9']; // แดงอ่อน, ส้มอ่อน, น้ำเงินอ่อน +const queueStore = useQueueStore() +const machineStore = useMachineStore() +const pageStore = usePageStore() +const dateStore = useDateStore() +const { currentDate: selectedDate } = storeToRefs(dateStore) +const pageContext = usePageContextStore() + +const ORDER_COLORS = ['#FFCDD2', '#FFE0B2', '#90CAF9'] // แดงอ่อน, ส้มอ่อน, น้ำเงินอ่อน const SPECIAL_COLORS: Record<string, string> = { - 'ผลิตเผื่อ': '#94f397', - 'เปลี่ยนขนาด': '#B3E5FC', -}; + ผลิตเผื่อ: '#94f397', + เปลี่ยนขนาด: '#B3E5FC' +} // ใช้ currentPage เป็น pagenum จาก pageContext const currentPage = computed({ get: () => pageContext.currentPage, - set: (val) => (pageContext.currentPage = val), -}); + set: (val) => (pageContext.currentPage = val) +}) // เมื่อเข้าหน้า ให้ดึงข้อมูลจาก backend onMounted(() => { - machineStore.fetchMachines(); - queueStore.fetchQueues(); - pageStore.fetchPages(); -}); + machineStore.fetchMachines() + queueStore.fetchQueues() + pageStore.fetchPages() +}) // State สำหรับ Gantt Chart -const showAddDialog = ref(false); -const startTime = ref('06:00'); -const endTime = ref('20:00'); +const showAddDialog = ref(false) +const startTime = ref('06:00') +const endTime = ref('20:00') -const draggingQueue = ref<any | null>(null); -const dragOffset = ref(0); -const resizingQueue = ref<any | null>(null); -const resizeDirection = ref<string | null>(null); -const localQueueMap = ref<Record<number, any>>({}); +const draggingQueue = ref<any | null>(null) +const dragOffset = ref(0) +const resizingQueue = ref<any | null>(null) +const resizeDirection = ref<string | null>(null) +const localQueueMap = ref<Record<number, any>>({}) -const ghostQueue = ref<any | null>(null); +const ghostQueue = ref<any | null>(null) const ghostStyle = reactive({ position: 'fixed', top: '0px', @@ -156,174 +204,173 @@ const ghostStyle = reactive({ pointerEvents: 'none', backgroundColor: '#4caf50', color: '#fff', - zIndex: 9999, -}); + zIndex: 9999 +}) -const originalLabelMap: Record<number, string> = {}; -const originalColorMap: Record<number, string> = {}; +const originalLabelMap: Record<number, string> = {} +const originalColorMap: Record<number, string> = {} // แปลงข้อมูล Queue จาก backend ให้เข้ากับรูปแบบที่ Gantt Chart ใช้ const formattedQueues = computed(() => { - return queueStore.queues.map(q => { - const isResizing = resizingQueue.value && resizingQueue.value.QueueID === q.QueueID; - const local = localQueueMap.value[q.QueueID]; + return queueStore.queues.map((q) => { + const isResizing = resizingQueue.value && resizingQueue.value.QueueID === q.QueueID + const local = localQueueMap.value[q.QueueID] const label = originalLabelMap[q.QueueID] ?? - (q.QueueType?.QueueTypeID === 2 ? 'ผลิตเผื่อ' : - q.QueueType?.QueueTypeID === 3 ? 'เปลี่ยนขนาด' : - local?.label || q.order?.customer?.name || 'Unknown'); + (q.QueueType?.QueueTypeID === 2 + ? 'ผลิตเผื่อ' + : q.QueueType?.QueueTypeID === 3 + ? 'เปลี่ยนขนาด' + : local?.label || q.order?.customer?.name || 'Unknown') // ✅ color logic - let color = ''; + let color = '' if (label === 'ผลิตเผื่อ' || label === 'เปลี่ยนขนาด') { - color = getQueueColor(label); // string-based + color = getQueueColor(label) // string-based } else { - color = getQueueColor(q.order?.OrderID ?? 0); // number-based + color = getQueueColor(q.order?.OrderID ?? 0) // number-based } - const startTime = isResizing ? resizingQueue.value.startTime : convertToLocalTime(q.startTime); - const finishTime = isResizing ? resizingQueue.value.finishTime : convertToLocalTime(q.finishTime); + const startTime = isResizing ? resizingQueue.value.startTime : convertToLocalTime(q.startTime) + const finishTime = isResizing + ? resizingQueue.value.finishTime + : convertToLocalTime(q.finishTime) return { QueueID: q.QueueID, - orderID: q.order?.OrderID || "Unknown", + orderID: q.order?.OrderID || 'Unknown', label, color, machineID: q.machine?.MachineID || 0, - machineName: q.machine?.name || "Unknown Machine", + machineName: q.machine?.name || 'Unknown Machine', startTime, finishTime, - status: q.status || "Unknown", - bottleSize: q.bottleSize || "Unknown", + status: q.status || 'Unknown', + bottleSize: q.bottleSize || 'Unknown', producedQuantity: q.producedQuantity || 0, pageNumber: q.page?.pagenum || 0, - QueueTypeID: q.QueueType?.QueueTypeID || 0, - }; - }); -}); - - - - + QueueTypeID: q.QueueType?.QueueTypeID || 0 + } + }) +}) function clearOriginalMaps() { - Object.keys(originalLabelMap).forEach((k) => delete originalLabelMap[+k]); - Object.keys(originalColorMap).forEach((k) => delete originalColorMap[+k]); + Object.keys(originalLabelMap).forEach((k) => delete originalLabelMap[+k]) + Object.keys(originalColorMap).forEach((k) => delete originalColorMap[+k]) } function convertToLocalTime(utcString: string): string { - const date = new Date(utcString); - date.setHours(date.getHours() + 7); - return date.toISOString().slice(0, 16).replace("T", " "); + const date = new Date(utcString) + date.setHours(date.getHours() + 7) + return date.toISOString().slice(0, 16).replace('T', ' ') } // Pagination & Dialog -const pages = computed(() => pageStore.pages.map(p => p.pagenum)); -const pageToShowDelete = ref<number | null>(null); -const selectedQueueItem = ref<any | null>(null); +const pages = computed(() => pageStore.pages.map((p) => p.pagenum)) +const pageToShowDelete = ref<number | null>(null) +const selectedQueueItem = ref<any | null>(null) watch( () => queueStore.queues, (newVal) => { - console.log("🔄 Queues updated in GanttChart.vue:", newVal); + console.log('🔄 Queues updated in GanttChart.vue:', newVal) }, { deep: true } -); +) const hours = computed(() => { - const startHour = parseInt(startTime.value.split(':')[0]); - const endHour = parseInt(endTime.value.split(':')[0]); - return Array.from({ length: endHour - startHour + 1 }, (_, i) => startHour + i); -}); + const startHour = parseInt(startTime.value.split(':')[0]) + const endHour = parseInt(endTime.value.split(':')[0]) + return Array.from({ length: endHour - startHour + 1 }, (_, i) => startHour + i) +}) // Methods function openQueueDialog(item: any) { - selectedQueueItem.value = { ...item }; + selectedQueueItem.value = { ...item } } function closeQueueDialog() { - selectedQueueItem.value = null; + selectedQueueItem.value = null } function openAddQueueDialog() { - selectedQueueItem.value = null; - showAddDialog.value = true; + selectedQueueItem.value = null + showAddDialog.value = true } function openAddQueueDialogForEdit(queueItem: any) { - selectedQueueItem.value = { ...queueItem }; - showAddDialog.value = true; + selectedQueueItem.value = { ...queueItem } + showAddDialog.value = true } -function handleDelete(queueItem: { QueueID: number; }) { - if (!queueItem) return; +function handleDelete(queueItem: { QueueID: number }) { + if (!queueItem) return queueStore .deleteQueue(queueItem.QueueID) .then(() => { - console.log(`✅ Queue ID ${queueItem.QueueID} ถูกลบสำเร็จ`); - closeAddQueueDialog(); + console.log(`✅ Queue ID ${queueItem.QueueID} ถูกลบสำเร็จ`) + closeAddQueueDialog() }) - .catch((error) => console.error(`❌ Error deleting queue:`, error)); + .catch((error) => console.error(`❌ Error deleting queue:`, error)) } function closeAddQueueDialog() { - showAddDialog.value = false; - selectedQueueItem.value = null; + showAddDialog.value = false + selectedQueueItem.value = null } function formatHour(hour: number): string { - return (hour < 10 ? '0' + hour : hour) + ':00'; + return (hour < 10 ? '0' + hour : hour) + ':00' } function getDateString(dateTimeStr: string): string { - return dateTimeStr.split(' ')[0]; + return dateTimeStr.split(' ')[0] } function getTimeString(dateTimeStr: string): string { - return dateTimeStr.split(' ')[1]; + return dateTimeStr.split(' ')[1] } function filteredQueue(machineID: number) { - return formattedQueues.value.filter(q => { - if (!q || !q.startTime || !q.finishTime) return false; - const queueDate = getDateString(q.startTime); + return formattedQueues.value.filter((q) => { + if (!q || !q.startTime || !q.finishTime) return false + const queueDate = getDateString(q.startTime) return ( q.machineID === machineID && q.pageNumber === currentPage.value && queueDate === selectedDate.value - ); - }); + ) + }) } -const orderColorMap: Record<number, string> = {}; +const orderColorMap: Record<number, string> = {} function getQueueColor(labelOrQueueID: string | number): string { // ✅ ถ้าเป็น label พิเศษ if (typeof labelOrQueueID === 'string') { if (SPECIAL_COLORS[labelOrQueueID]) { - return SPECIAL_COLORS[labelOrQueueID]; + return SPECIAL_COLORS[labelOrQueueID] } - return '#CCCCCC'; // fallback + return '#CCCCCC' // fallback } // ✅ ถ้าเป็น orderID → แจกสีตาม index - const orderID = labelOrQueueID; - if (orderColorMap[orderID]) return orderColorMap[orderID]; + const orderID = labelOrQueueID + if (orderColorMap[orderID]) return orderColorMap[orderID] - const index = Object.keys(orderColorMap).length % ORDER_COLORS.length; - const color = ORDER_COLORS[index]; - orderColorMap[orderID] = color; - return color; + const index = Object.keys(orderColorMap).length % ORDER_COLORS.length + const color = ORDER_COLORS[index] + orderColorMap[orderID] = color + return color } - 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%)`; + 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: 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 leftPercent = ((startDecimal - timeToDecimal(startTime.value)) / totalHours) * 100; - const widthPercent = ((endDecimal - startDecimal) / totalHours) * 100; + const start = getTimeString(item.startTime) + const end = getTimeString(item.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 return { left: `${leftPercent}%`, @@ -338,299 +385,284 @@ function getQueueStyle(item: any) { padding: '5px', fontSize: '14px', fontWeight: 'bold', - boxShadow: '0px 4px 6px rgba(0, 0, 0, 0.1)', - }; + boxShadow: '0px 4px 6px rgba(0, 0, 0, 0.1)' + } } - - - function getLineStyle(hour: number) { - const totalHours = hours.value.length; - const leftPercent = ((hour - timeToDecimal(startTime.value)) / totalHours) * 100; - return { left: `${leftPercent}%` }; + const totalHours = hours.value.length + const leftPercent = ((hour - timeToDecimal(startTime.value)) / totalHours) * 100 + return { left: `${leftPercent}%` } } function timeToDecimal(timeStr: string): number { - const [h, m] = timeStr.split(':').map(Number); - return h + m / 60; + const [h, m] = timeStr.split(':').map(Number) + return h + m / 60 } function decimalToTime(decimal: number): string { - const hrs = Math.floor(decimal); - const minutes = Math.round((decimal - hrs) * 60); - return (hrs < 10 ? '0' + hrs : hrs) + ':' + (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: 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); + 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) // 🟢 Copy พร้อม label - ghostQueue.value = { ...item, label: item.label }; - ghostStyle.display = 'block'; - ghostStyle.backgroundColor = getQueueColor(item.label); // เปลี่ยนจาก orderID เป็น label + ghostQueue.value = { ...item, label: item.label } + ghostStyle.display = 'block' + ghostStyle.backgroundColor = getQueueColor(item.label) // เปลี่ยนจาก orderID เป็น label - document.addEventListener('dragover', onDragOverGlobal); - document.addEventListener('dragend', onDragEndGlobal); + document.addEventListener('dragover', onDragOverGlobal) + document.addEventListener('dragend', onDragEndGlobal) } function onDragOver(event: DragEvent) { - event.preventDefault(); + event.preventDefault() } function onDragOverGlobal(event: DragEvent) { - event.preventDefault(); - const rowTimeline = document.querySelector('.row-timeline') as HTMLElement; - 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 timelineStartDec = timeToDecimal(startTime.value); - const timelineEndDec = timeToDecimal(endTime.value); - if (!draggingQueue.value) return; - 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; + event.preventDefault() + const rowTimeline = document.querySelector('.row-timeline') as HTMLElement + 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 timelineStartDec = timeToDecimal(startTime.value) + const timelineEndDec = timeToDecimal(endTime.value) + if (!draggingQueue.value) return + 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 if (newEndDecimal > timelineEndDec) { - newEndDecimal = timelineEndDec; - newStartDecimal = newEndDecimal - duration; + newEndDecimal = timelineEndDec + newStartDecimal = newEndDecimal - duration } - const datePart = getDateString(draggingQueue.value.startTime); - const newStartStr = datePart + ' ' + decimalToTime(newStartDecimal); - const newEndStr = datePart + ' ' + decimalToTime(newEndDecimal); + const datePart = getDateString(draggingQueue.value.startTime) + const newStartStr = datePart + ' ' + decimalToTime(newStartDecimal) + const newEndStr = datePart + ' ' + decimalToTime(newEndDecimal) if (ghostQueue.value) { - ghostQueue.value.startTime = newStartStr; - ghostQueue.value.finishTime = newEndStr; + ghostQueue.value.startTime = newStartStr + 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; + 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; + const rows = document.querySelectorAll('.row') + let closestRow: HTMLElement | null = null + let minDistance = Infinity rows.forEach((row) => { - const rect = (row as HTMLElement).getBoundingClientRect(); - const distance = Math.abs(event.clientY - rect.top); + const rect = (row as HTMLElement).getBoundingClientRect() + const distance = Math.abs(event.clientY - rect.top) if (distance < minDistance) { - minDistance = distance; - closestRow = row as HTMLElement; + minDistance = distance + closestRow = row as HTMLElement } - }); + }) if (closestRow) { - const machineIDString = (closestRow as HTMLElement).dataset.machineId; + const machineIDString = (closestRow as HTMLElement).dataset.machineId if (machineIDString) { - ghostQueue.value.machineID = parseInt(machineIDString, 10); + ghostQueue.value.machineID = parseInt(machineIDString, 10) } - ghostStyle.top = (closestRow as HTMLElement).getBoundingClientRect().top + 'px'; + ghostStyle.top = (closestRow as HTMLElement).getBoundingClientRect().top + 'px' } - ghostStyle.left = snappedLeft + 'px'; - ghostStyle.width = snappedWidth + 'px'; + ghostStyle.left = snappedLeft + 'px' + ghostStyle.width = snappedWidth + 'px' } function onDragEndGlobal() { - ghostQueue.value = null; - ghostStyle.display = 'none'; - draggingQueue.value = null; - document.removeEventListener('dragend', onDragEndGlobal); + ghostQueue.value = null + ghostStyle.display = 'none' + draggingQueue.value = null + document.removeEventListener('dragend', onDragEndGlobal) } async function onDragEnd(event: DragEvent, item: any) { - if (!ghostQueue.value) return; + if (!ghostQueue.value) return - item.startTime = ghostQueue.value.startTime; - item.finishTime = ghostQueue.value.finishTime; - const newMachineID = ghostQueue.value.machine?.MachineID || item.machineID; + item.startTime = ghostQueue.value.startTime + item.finishTime = ghostQueue.value.finishTime + const newMachineID = ghostQueue.value.machine?.MachineID || item.machineID // 🟡 Save original label & color - originalLabelMap[item.QueueID] = item.label; - originalColorMap[item.QueueID] = getQueueColor(item.label); + originalLabelMap[item.QueueID] = item.label + originalColorMap[item.QueueID] = getQueueColor(item.label) await queueStore.updateQueue(item.QueueID, { startTime: item.startTime, finishTime: item.finishTime, - MachineID: newMachineID, - }); + MachineID: newMachineID + }) - await queueStore.fetchQueues(); - clearOriginalMaps(); + await queueStore.fetchQueues() + clearOriginalMaps() - ghostQueue.value = null; - ghostStyle.display = 'none'; - draggingQueue.value = null; + ghostQueue.value = null + ghostStyle.display = 'none' + draggingQueue.value = null } - - async function onDrop(event: DragEvent, newMachineID: number) { - event.preventDefault(); - if (!draggingQueue.value || !ghostQueue.value) return; + event.preventDefault() + if (!draggingQueue.value || !ghostQueue.value) return - const queueID = draggingQueue.value.QueueID; + const queueID = draggingQueue.value.QueueID // 🟡 Save label & color - originalLabelMap[queueID] = draggingQueue.value.label; - originalColorMap[queueID] = getQueueColor(draggingQueue.value.label); + originalLabelMap[queueID] = draggingQueue.value.label + originalColorMap[queueID] = getQueueColor(draggingQueue.value.label) - draggingQueue.value.startTime = ghostQueue.value.startTime; - draggingQueue.value.finishTime = ghostQueue.value.finishTime; - draggingQueue.value.machineID = newMachineID; + draggingQueue.value.startTime = ghostQueue.value.startTime + draggingQueue.value.finishTime = ghostQueue.value.finishTime + draggingQueue.value.machineID = newMachineID try { await queueStore.updateQueue(queueID, { startTime: draggingQueue.value.startTime, finishTime: draggingQueue.value.finishTime, - MachineID: newMachineID, - }); + MachineID: newMachineID + }) - await queueStore.fetchQueues(); - clearOriginalMaps(); + await queueStore.fetchQueues() + clearOriginalMaps() - delete originalLabelMap[queueID]; - delete originalColorMap[queueID]; + delete originalLabelMap[queueID] + delete originalColorMap[queueID] } catch (error) { - console.error(`❌ Error updating queue:`, error); + console.error(`❌ Error updating queue:`, error) } - ghostQueue.value = null; - ghostStyle.display = 'none'; - draggingQueue.value = null; + ghostQueue.value = null + ghostStyle.display = 'none' + draggingQueue.value = null } - function onResizeStart(event: MouseEvent, item: any, direction: string) { - event.preventDefault(); - resizingQueue.value = item; - resizeDirection.value = direction; + event.preventDefault() + resizingQueue.value = item + resizeDirection.value = direction // 🧠 เก็บสำเนาไว้ก่อนถูก fetch เคลียร์ - localQueueMap.value[item.QueueID] = { ...item }; + localQueueMap.value[item.QueueID] = { ...item } - document.addEventListener('mousemove', onResizing); - document.addEventListener('mouseup', onResizeEnd); + document.addEventListener('mousemove', onResizing) + document.addEventListener('mouseup', onResizeEnd) } - - function onResizing(event: MouseEvent) { - if (!resizingQueue.value) return; + if (!resizingQueue.value) return - const rowTimeline = document.querySelector('.row-timeline') as HTMLElement; - if (!rowTimeline) return; + const rowTimeline = document.querySelector('.row-timeline') as HTMLElement + if (!rowTimeline) return - const timelineRect = rowTimeline.getBoundingClientRect(); - const timelineWidth = timelineRect.width; - const offsetX = event.clientX - timelineRect.left; + 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); + let ratio = offsetX / timelineWidth + ratio = Math.min(Math.max(ratio, 0), 1) - const timelineStartDec = timeToDecimal(startTime.value); - const timelineEndDec = timeToDecimal(endTime.value); + const timelineStartDec = timeToDecimal(startTime.value) + const timelineEndDec = timeToDecimal(endTime.value) - let newTimeDecimal = timelineStartDec + ratio * (timelineEndDec - timelineStartDec); - newTimeDecimal = Math.round(newTimeDecimal * 2) / 2; // snap 30 นาที + let newTimeDecimal = timelineStartDec + ratio * (timelineEndDec - timelineStartDec) + newTimeDecimal = Math.round(newTimeDecimal * 2) / 2 // snap 30 นาที - const datePart = getDateString(resizingQueue.value.startTime); - const startDec = timeToDecimal(getTimeString(resizingQueue.value.startTime)); - const endDec = timeToDecimal(getTimeString(resizingQueue.value.finishTime)); + const datePart = getDateString(resizingQueue.value.startTime) + const startDec = timeToDecimal(getTimeString(resizingQueue.value.startTime)) + const endDec = timeToDecimal(getTimeString(resizingQueue.value.finishTime)) if (resizeDirection.value === 'left') { - if (newTimeDecimal >= endDec) return; - resizingQueue.value.startTime = datePart + ' ' + decimalToTime(newTimeDecimal); + if (newTimeDecimal >= endDec) return + resizingQueue.value.startTime = datePart + ' ' + decimalToTime(newTimeDecimal) } else if (resizeDirection.value === 'right') { - if (newTimeDecimal <= startDec) return; - resizingQueue.value.finishTime = datePart + ' ' + decimalToTime(newTimeDecimal); + if (newTimeDecimal <= startDec) return + resizingQueue.value.finishTime = datePart + ' ' + decimalToTime(newTimeDecimal) } } - - async function onResizeEnd() { - if (!resizingQueue.value) return; + if (!resizingQueue.value) return - const item = resizingQueue.value; + const item = resizingQueue.value try { await queueStore.updateQueue(item.QueueID, { startTime: item.startTime, - finishTime: item.finishTime, - }); + finishTime: item.finishTime + }) // ✅ ค่อย fetch หลัง update เสร็จ - await queueStore.fetchQueues(); + await queueStore.fetchQueues() } catch (error) { - console.error('❌ Resize update failed:', error); + console.error('❌ Resize update failed:', error) } finally { // ✅ ย้ายมาปิดตรงนี้ เพื่อให้ formattedQueues ยังแสดง item เดิมระหว่างรอ fetch - resizingQueue.value = null; - resizeDirection.value = null; + resizingQueue.value = null + resizeDirection.value = null - document.removeEventListener('mousemove', onResizing); - document.removeEventListener('mouseup', onResizeEnd); + document.removeEventListener('mousemove', onResizing) + document.removeEventListener('mouseup', onResizeEnd) } } - - - - - // Pagination Functions async function addPage() { if (pageStore.pages.length < 10) { - await pageStore.addPage(); - await pageStore.fetchPages(); // 👈 โหลดหน้าทั้งหมดใหม่ + await pageStore.addPage() + await pageStore.fetchPages() // 👈 โหลดหน้าทั้งหมดใหม่ } else { - alert('Maximum of 10 pages allowed.'); + alert('Maximum of 10 pages allowed.') } } function onPageRightClick(page: number, event: MouseEvent) { - event.preventDefault(); - pageToShowDelete.value = page; + event.preventDefault() + pageToShowDelete.value = page } - async function deletePage(pageNum: number) { - await pageStore.removePage(pageNum); - await pageStore.fetchPages(); // 👈 โหลดหน้าทั้งหมดใหม่ + await pageStore.removePage(pageNum) + await pageStore.fetchPages() // 👈 โหลดหน้าทั้งหมดใหม่ if (currentPage.value === pageNum) { - const remaining = pageStore.pages.map(p => p.pagenum); - currentPage.value = remaining.length > 0 ? remaining[0] : 1; + const remaining = pageStore.pages.map((p) => p.pagenum) + currentPage.value = remaining.length > 0 ? remaining[0] : 1 } - pageToShowDelete.value = null; + pageToShowDelete.value = null } function onDocumentClick(event: MouseEvent) { - const target = event.target as HTMLElement; + const target = event.target as HTMLElement if (!target.closest('.page-btn')) { - pageToShowDelete.value = null; + pageToShowDelete.value = null } } onMounted(() => { - document.addEventListener('click', onDocumentClick); -}); + document.addEventListener('click', onDocumentClick) +}) onBeforeUnmount(() => { - document.removeEventListener('click', onDocumentClick); -}); -</script> \ No newline at end of file + document.removeEventListener('click', onDocumentClick) +}) +</script> diff --git a/src/components/GanttChart/OrderDialog.vue b/src/components/GanttChart/OrderDialog.vue index cecca831f55bc81d0824d973b3769bdffcbe0874..92dbd29fec66deccb4223073ff605a78f60e7d0a 100644 --- a/src/components/GanttChart/OrderDialog.vue +++ b/src/components/GanttChart/OrderDialog.vue @@ -2,7 +2,7 @@ <div class="order-dialog-overlay"> <div class="order-dialog" :style="{ backgroundColor: color }"> <div class="dialog-header"> - <h3 class="order-title">QueueID: {{ queueItem?.QueueID || 'N/A' }}</h3> + <h3 class="order-title" >QueueID: {{ queueItem?.QueueID || 'N/A' }}</h3> <div class="dialog-buttons"> <button @click="$emit('edit', queueItem)" class="icon-button"> <v-icon>mdi-pencil</v-icon> @@ -105,13 +105,13 @@ } .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); + /* background จะถูก override ด้วย inline style จาก :style="{ backgroundColor: color }" */ + 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 { @@ -124,7 +124,7 @@ font-size: 1.2rem; font-weight: bold; text-align: left; - color: white; + color: black; } .dialog-buttons { diff --git a/src/stores/dateStore.ts b/src/stores/dateStore.ts index bc09a60348d63ec5c958ca21e375520be79eb57c..216b2c8439fb7e4cef9b8bbfa1503fc5ed1a315e 100644 --- a/src/stores/dateStore.ts +++ b/src/stores/dateStore.ts @@ -2,8 +2,8 @@ import { defineStore } from 'pinia' import { ref } from 'vue' export const useDateStore = defineStore('dateStore', () => { - // 🔸 ใช้ default เป็น "2025-01-01" - const currentDate = ref('2025-01-01') + // ✅ ใช้วันปัจจุบันเป็นค่าเริ่มต้น + const currentDate = ref(new Date().toISOString().slice(0, 10)) // ✅ อัปเดตวันที่ const setDate = (newDate: string) => {