diff --git a/src/components/GanttChart/GanttChart.vue b/src/components/GanttChart/GanttChart.vue index d30c1c8f1011c569ac2177925859602b9dcfaedc..fb9a3229e99bc8b1116a1233e9c369d134fe3ee7 100644 --- a/src/components/GanttChart/GanttChart.vue +++ b/src/components/GanttChart/GanttChart.vue @@ -63,7 +63,7 @@ @dragend="onDragEnd($event, item)" @click.stop="openQueueDialog(item)" > - {{ item.orderID }} ({{ getTimeString(item.startTime) }} - {{ getTimeString(item.finishTime) }}) + {{ item.label }} ({{ getTimeString(item.startTime) }} - {{ getTimeString(item.finishTime) }}) </div> <!-- Handle สำหรับ Resize ด้านขวา --> <div class="resize-handle right" @mousedown="onResizeStart($event, item, 'right')"></div> @@ -190,23 +190,45 @@ const ghostStyle = reactive({ zIndex: 9999, }); +const originalLabelMap: Record<number, string> = {}; +const originalColorMap: Record<number, string> = {}; + // แปลงข้อมูล 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, - // เปลี่ยนจาก PageID เป็น pagenum - pageNumber: q.page?.pagenum || 0, - })); + return queueStore.queues.map(q => { + const label = + originalLabelMap[q.QueueID] ?? + (q.QueueType?.QueueTypeID === 2 ? 'ผลิตเผื่อ' : + q.QueueType?.QueueTypeID === 3 ? 'เปลี่ยนขนาด' : + q.order?.customer?.name || 'Unknown'); + + const color = originalColorMap[q.QueueID] ?? getQueueColor(label); + + return { + QueueID: q.QueueID, + orderID: q.order?.OrderID || "Unknown", + label, + color, // เพิ่มตรงนี้ไว้ใช้ใน getQueueStyle + 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?.pagenum || 0, + QueueTypeID: q.QueueType?.QueueTypeID || 0, + }; + }); }); + + +function clearOriginalMaps() { + 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); @@ -285,20 +307,23 @@ function filteredQueue(machineID: number) { } const colorMap: Record<string, string> = {}; -function getQueueColor(orderID: string | number): string { - const key = String(orderID); - if (key === "ผลิตเผื่อ") return "#FFC1C1"; - if (key === "เปลี่ยนขนาด") return "#E1BEE7"; - if (colorMap[key]) return colorMap[key]; + function getQueueColor(label: string): string { + if (label === "ผลิตเผื่อ") return "#FFC1C1"; + if (label === "เปลี่ยนขนาด") return "#E1BEE7"; + + if (colorMap[label]) return colorMap[label]; + const newColor = generateWaterPastel(); - colorMap[key] = newColor; + colorMap[label] = 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: any) { const start = getTimeString(item.startTime); const end = getTimeString(item.finishTime); @@ -307,13 +332,11 @@ function getQueueStyle(item: any) { const totalHours = hours.value.length; const leftPercent = ((startDecimal - timeToDecimal(startTime.value)) / totalHours) * 100; const widthPercent = ((endDecimal - startDecimal) / totalHours) * 100; - 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: item.color, // ✅ ใช้ field color color: '#333', borderRadius: '10px', textAlign: 'center', @@ -326,6 +349,10 @@ function getQueueStyle(item: any) { 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; @@ -350,12 +377,16 @@ function onDragStart(event: DragEvent, item: any) { const emptyImg = new Image(); emptyImg.src = 'data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs='; event.dataTransfer?.setDragImage(emptyImg, 0, 0); - ghostQueue.value = { ...item }; + + // 🟢 Copy พร้อม label + ghostQueue.value = { ...item, label: item.label }; ghostStyle.display = 'block'; - ghostStyle.backgroundColor = getQueueColor(item.order?.OrderID || "Unknown"); + ghostStyle.backgroundColor = getQueueColor(item.label); // เปลี่ยนจาก orderID เป็น label + document.addEventListener('dragover', onDragOverGlobal); document.addEventListener('dragend', onDragEndGlobal); } + function onDragOver(event: DragEvent) { event.preventDefault(); } @@ -430,51 +461,79 @@ function onDragEndGlobal() { draggingQueue.value = null; document.removeEventListener('dragend', onDragEndGlobal); } + 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; const newMachineID = ghostQueue.value.machine?.MachineID || item.machineID; + + // 🟡 Save original label & color + originalLabelMap[item.QueueID] = item.label; + originalColorMap[item.QueueID] = getQueueColor(item.label); + await queueStore.updateQueue(item.QueueID, { startTime: item.startTime, finishTime: item.finishTime, MachineID: newMachineID, }); + + await queueStore.fetchQueues(); + clearOriginalMaps(); + ghostQueue.value = null; ghostStyle.display = 'none'; draggingQueue.value = null; } + + + async function onDrop(event: DragEvent, newMachineID: number) { event.preventDefault(); if (!draggingQueue.value || !ghostQueue.value) return; - console.log(`🔄 Dropped Queue ${draggingQueue.value.QueueID} to Machine ${newMachineID}`); + + const queueID = draggingQueue.value.QueueID; + + // 🟡 Save label & color + 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; + try { - await queueStore.updateQueue(draggingQueue.value.QueueID, { + await queueStore.updateQueue(queueID, { startTime: draggingQueue.value.startTime, finishTime: draggingQueue.value.finishTime, MachineID: newMachineID, }); - console.log(`✅ Queue ${draggingQueue.value.QueueID} updated to Machine ${newMachineID}`); + + await queueStore.fetchQueues(); + clearOriginalMaps(); + + delete originalLabelMap[queueID]; + delete originalColorMap[queueID]; } catch (error) { console.error(`❌ Error updating queue:`, error); } + ghostQueue.value = null; ghostStyle.display = 'none'; draggingQueue.value = null; } + function onResizeStart(event: MouseEvent, item: any, direction: string) { event.preventDefault(); - ghostQueue.value = { ...item }; + ghostQueue.value = { ...item, label: item.label }; // 🟢 คง label เดิมไว้ 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; @@ -501,21 +560,36 @@ function onResizing(event: MouseEvent) { ghostQueue.value.finishTime = newEndStr; } } + async function onResizeEnd() { - if (!resizingQueue.value) return; - console.log(`🔄 Updating Queue ${resizingQueue.value.QueueID} after resize...`); - await queueStore.updateQueue(resizingQueue.value.QueueID, { + if (!resizingQueue.value || !ghostQueue.value) return; + + const queueID = resizingQueue.value.QueueID; + + // 🟡 Save original label & color + originalLabelMap[queueID] = resizingQueue.value.label; + originalColorMap[queueID] = getQueueColor(resizingQueue.value.label); + + await queueStore.updateQueue(queueID, { startTime: ghostQueue.value.startTime, finishTime: ghostQueue.value.finishTime, }); + + await queueStore.fetchQueues(); + clearOriginalMaps(); + resizingQueue.value = null; resizeDirection.value = null; ghostQueue.value = null; ghostStyle.display = 'none'; + document.removeEventListener('mousemove', onResizing); document.removeEventListener('mouseup', onResizeEnd); } + + + // Pagination Functions async function addPage() { if (pageStore.pages.length < 10) { @@ -530,6 +604,7 @@ function onPageRightClick(page: number, event: MouseEvent) { pageToShowDelete.value = page; } + async function deletePage(pageNum: number) { await pageStore.removePage(pageNum); await pageStore.fetchPages(); // 👈 โหลดหน้าทั้งหมดใหม่ diff --git a/src/components/ProductionTargetTable.vue b/src/components/ProductionTargetTable.vue index 7c7b139e3c18b7c4ddd26aa7a3a8df00fd086260..75fbc1a4bd0ae9848e59fbd82a380869c98cadb9 100644 --- a/src/components/ProductionTargetTable.vue +++ b/src/components/ProductionTargetTable.vue @@ -15,7 +15,7 @@ const currentPage = computed(() => pageContext.currentPage) const headers = ref([ { title: "รหัส", key: "id" }, - { title: "ชื่อ", key: "name" }, + { title: "ชื่อ", key: "name" }, { title: "ขนาด", key: "size" }, { title: "แบรนด์", key: "brand" }, { title: "ชนิด", key: "type" }, @@ -25,7 +25,7 @@ const headers = ref([ { title: "เหลือ", key: "remaining" }, ]) -const groupBy = [{ key: 'order', order: 'asc' }] +const groupBy = [{ key: 'customerGroup' }] const savingRowIds = ref<number[]>([]) const recentlySavedIds = ref<number[]>([]) @@ -34,25 +34,60 @@ onMounted(async () => { }) // ✅ กรองตาม PageID + selectedDate จาก store -const productionData = computed(() => - productionTargetStore.productionTargets +const productionData = computed(() => { + const rawData = productionTargetStore.productionTargets .filter((target) => target.page?.PageID === currentPage.value && target.Date?.slice(0, 10) === selectedDate.value ) - .map((target) => ({ - id: target.ProductionTargetID, - name: target.item?.name || target.item?.brand || 'ไม่พบข้อมูล', - size: target.item?.size || '-', - brand: target.item?.brand || '-', - type: target.itemType === 'PRODUCT' ? 'Product' : 'Material', - target: target.TargetProduced, - produced: target.ActualProduced, - unit: target.item?.unit || '-', - remaining: target.Status, - order: target.order?.OrderID || `Order #${target.order?.OrderID}` || 'ไม่ระบุ', - })) -) + .map((target) => { + const customerName = target.order?.customer?.name || 'ไม่ระบุลูกค้า' + return { + id: target.ProductionTargetID, + name: target.item?.name || target.item?.brand || 'ไม่พบข้อมูล', + size: target.item?.size || '-', + brand: target.item?.brand || '-', + type: target.itemType === 'PRODUCT' ? 'Product' : 'Material', + target: target.TargetProduced, + produced: target.ActualProduced, + unit: target.item?.unit || '-', + remaining: target.Status, + order: target.order?.OrderID || 'ไม่ระบุ', + customer: customerName, + } + }) + + // 🔄 รวมรายการซ้ำในกลุ่ม "วันนี้" + const todayGroupMap = new Map<string, any>() + + rawData.forEach(item => { + const key = `${item.name}|${item.size}|${item.brand}|${item.type}` + if (todayGroupMap.has(key)) { + const existing = todayGroupMap.get(key) + existing.target += item.target + existing.produced += item.produced + } else { + todayGroupMap.set(key, { + ...item, + customerGroup: '0-รวมทั้งหมด', // กลุ่ม "วันนี้" + }) + } + }) + + const todayItems = Array.from(todayGroupMap.values()) + + // ✅ กลุ่มรายลูกค้า (ไม่ยุบ) + const customerItems = rawData.map(item => ({ + ...item, + customerGroup: `1-${item.customer}`, + })) + + return [...todayItems, ...customerItems] +}) + + + + async function saveActualProduced(item: any) { const id = item.id @@ -97,22 +132,25 @@ function blockInvalidKeys(event: KeyboardEvent) { item-value="name" hide-default-footer > - <template v-slot:group-header="{ item, columns, toggleGroup, isGroupOpen }"> - <tr> - <td :colspan="columns.length"> - <div class="d-flex align-center"> - <v-btn - :icon="isGroupOpen(item) ? '$expand' : '$next'" - density="comfortable" - size="x-small" - variant="outlined" - @click="toggleGroup(item)" - ></v-btn> - <span class="ms-2 text-caption">Order {{ item.value }}</span> - </div> - </td> - </tr> - </template> + <template v-slot:group-header="{ item, columns, toggleGroup, isGroupOpen }"> + <tr> + <td :colspan="columns.length"> + <div class="d-flex align-center"> + <v-btn + :icon="isGroupOpen(item) ? '$expand' : '$next'" + density="comfortable" + size="x-small" + variant="outlined" + @click="toggleGroup(item)" + /> + <span class="ms-2 text-caption"> + {{ item.value.startsWith('0-') ? 'วันนี้' : 'ลูกค้า: ' + item.value.slice(2) }} + </span> + </div> + </td> + </tr> + </template> + <template v-slot:item.produced="{ item }"> <div class="save-cell-wrapper" style="position: relative; display: inline-block;"> @@ -146,6 +184,7 @@ function blockInvalidKeys(event: KeyboardEvent) { <style scoped> .custom-table { font-size: 12px; + font-family: 'Kanit'; } .fade-check { @@ -156,4 +195,5 @@ function blockInvalidKeys(event: KeyboardEvent) { 80% { opacity: 1; transform: scale(1.2); } 100% { opacity: 0; transform: scale(1); } } + </style>