diff --git a/src/components/GanttChart/GanttChart.vue b/src/components/GanttChart/GanttChart.vue index 95d50cbf8e84bab6742812f4f7b047e5e1b40dc6..aadebd532b8c01c6ac5b0bbd0d91f4e59b03b501 100644 --- a/src/components/GanttChart/GanttChart.vue +++ b/src/components/GanttChart/GanttChart.vue @@ -112,6 +112,12 @@ const dateStore = useDateStore(); const { currentDate: selectedDate } = storeToRefs(dateStore); const pageContext = usePageContextStore(); +const ORDER_COLORS = ['#FFCDD2', '#FFE0B2', '#90CAF9']; // แดงอ่อน, ส้มอ่อน, น้ำเงินอ่อน +const SPECIAL_COLORS: Record<string, string> = { + 'ผลิตเผื่อ': '#9E9E9E', // เทา + 'เปลี่ยนขนาด': '#B3E5FC', // ฟ้าอ่อน +}; + // ใช้ currentPage เป็น pagenum จาก pageContext const currentPage = computed({ get: () => pageContext.currentPage, @@ -168,12 +174,17 @@ const formattedQueues = computed(() => { q.QueueType?.QueueTypeID === 3 ? 'เปลี่ยนขนาด' : local?.label || q.order?.customer?.name || 'Unknown'); - const color = originalColorMap[q.QueueID] ?? getQueueColor(label); + // ✅ color logic + let color = ''; + if (label === 'ผลิตเผื่อ' || label === 'เปลี่ยนขนาด') { + color = getQueueColor(label); // string-based + } else { + 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); - return { QueueID: q.QueueID, orderID: q.order?.OrderID || "Unknown", @@ -195,6 +206,7 @@ const formattedQueues = computed(() => { + function clearOriginalMaps() { Object.keys(originalLabelMap).forEach((k) => delete originalLabelMap[+k]); Object.keys(originalColorMap).forEach((k) => delete originalColorMap[+k]); @@ -277,18 +289,27 @@ function filteredQueue(machineID: number) { }); } -const colorMap: Record<string, string> = {}; -function getQueueColor(label: string): string { - if (label === "ผลิตเผื่อ") return "#FFC1C1"; - if (label === "เปลี่ยนขนาด") return "#E1BEE7"; +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 '#CCCCCC'; // fallback + } - if (colorMap[label]) return colorMap[label]; + // ✅ ถ้าเป็น orderID → แจกสีตาม index + const orderID = labelOrQueueID; + if (orderColorMap[orderID]) return orderColorMap[orderID]; - const newColor = generateWaterPastel(); - colorMap[label] = newColor; - return newColor; + 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)]; diff --git a/src/components/GanttChart/PrioritySetting.vue b/src/components/GanttChart/PrioritySetting.vue index 239cc59fdd13f84c3ecf62040ac298b4a888bfec..5b95633e3a1b4c8db98a8ef77d853fe4b34c54ca 100644 --- a/src/components/GanttChart/PrioritySetting.vue +++ b/src/components/GanttChart/PrioritySetting.vue @@ -14,8 +14,9 @@ const orders = computed(() => { .sort((a, b) => a.priority - b.priority) .map((p) => ({ id: p.order.OrderID, - name: `ORDER${p.order.OrderID}`, + name: `Order ${p.order.OrderID}`, orderPriorityID: p.orderPriorityID, + customerName: p.order.customer?.name ?? 'ไม่ระบุลูกค้า', // ✅ เพิ่มตรงนี้ })); }); @@ -60,7 +61,7 @@ const handleDrop = async (targetOrder: any) => { <transition-group name="list" tag="div"> <div v-for="order in orders" :key="order.id" class="order-item" draggable="true" @dragstart="() => handleDragStart(order)" @dragover="handleDragOver" @drop="() => handleDrop(order)"> - {{ order.name }} + {{ order.name }} - {{ order.customerName }} </div> </transition-group> </v-card> diff --git a/src/components/GanttChart/StockConfig.vue b/src/components/GanttChart/StockConfig.vue index bb05fb2361ca30cfba3e5e36eebe994aa77a8e8e..a5438f4b3c7105a1a2d5843b5453cf81c2d60584 100644 --- a/src/components/GanttChart/StockConfig.vue +++ b/src/components/GanttChart/StockConfig.vue @@ -1,112 +1,182 @@ -<template> - <v-card class="pa-5 dialog-card"> - <p class="text-center font-weight-bold mb-4 text-white" style="font-size: 20px;"> - หน้าจัดแต่งการเก็บ - </p> - - <v-table dense class="product-table"> - <thead> - <tr> - <th>ลำดับความสำคัญ</th> - <th>ชื่อ</th> - <th>ชนิด</th> - <th>แบรนด์</th> - <th>จำนวนที่ต้องการเก็บ</th> - <th>หน่วย</th> - </tr> - </thead> - <transition-group tag="tbody" name="list"> - <tr v-for="config in stockConfigStore.stockConfigs" :key="config.StockConfigID" draggable="true" - @dragstart="() => handleDragStart(config)" @dragover="handleDragOver" @drop="() => handleDrop(config)"> - <td class="drag-handle">☰</td> - <td>{{ config.itemName }}</td> - <td>{{ config.itemType }}</td> - <td>{{ config.itemDetail?.brand ?? '-' }}</td> - <td>{{ (config.targetStockLevel ?? 0).toLocaleString() }}</td> - <td>{{ config.itemDetail?.unit ?? '-' }}</td> - </tr> - - </transition-group> - </v-table> - - <!-- Toggle & Action Buttons --> - <div class="action-section"> - <v-switch color="primary" v-model="stockConfigStore.isProductionSpare" - :label="stockConfigStore.productionSpareLabel" class="switch-toggle" @change="onSwitchChange" /> - </div> - </v-card> -</template> - <script setup> -import { ref, onMounted } from 'vue'; -import { useStockConfigStore } from '@/stores/stockConfig'; +import { ref, onMounted } from 'vue' +import { useStockConfigStore } from '@/stores/stockConfig' -const stockConfigStore = useStockConfigStore(); +const stockConfigStore = useStockConfigStore() // ดึงข้อมูล StockConfig จาก backend เมื่อ component mount onMounted(() => { stockConfigStore.fetchStockConfigs().then(() => { - console.log('✅ ดึงได้:', stockConfigStore.stockConfigs); - }); -}); + console.log('✅ ดึงได้:', stockConfigStore.stockConfigs) + }) +}) // สำหรับ drag & drop -const draggedConfig = ref(null); +const draggedConfig = ref(null) +// สำหรับสถานะการบันทึกแต่ละแถว +const savingRowIds = ref([]) +const recentlySavedIds = ref([]) function handleDragStart(config) { - draggedConfig.value = config; + draggedConfig.value = config } function handleDragOver(event) { - event.preventDefault(); + event.preventDefault() } async function handleDrop(targetConfig) { - if ( - !draggedConfig.value || - draggedConfig.value.StockConfigID === targetConfig.StockConfigID - ) - return; + if (!draggedConfig.value || draggedConfig.value.StockConfigID === targetConfig.StockConfigID) + return - const originalConfigs = stockConfigStore.stockConfigs; + const originalConfigs = stockConfigStore.stockConfigs const draggedIndex = originalConfigs.findIndex( (c) => c.StockConfigID === draggedConfig.value.StockConfigID - ); + ) const targetIndex = originalConfigs.findIndex( (c) => c.StockConfigID === targetConfig.StockConfigID - ); + ) - if (draggedIndex === -1 || targetIndex === -1) return; + if (draggedIndex === -1 || targetIndex === -1) return - const newConfigs = [...originalConfigs]; - const [removed] = newConfigs.splice(draggedIndex, 1); - newConfigs.splice(targetIndex, 0, removed); + const newConfigs = [...originalConfigs] + const [removed] = newConfigs.splice(draggedIndex, 1) + newConfigs.splice(targetIndex, 0, removed) const updatedPayload = { configs: newConfigs.map((config, index) => ({ id: config.StockConfigID, priorityLevel: index + 1, targetStockLevel: config.targetStockLevel, - status: config.status, - })), - }; + status: config.status + })) + } - await stockConfigStore.batchUpdateStockConfigs(updatedPayload); + await stockConfigStore.batchUpdateStockConfigs(updatedPayload) stockConfigStore.stockConfigs = newConfigs.map((c, idx) => ({ ...c, - priorityLevel: idx + 1, - })); + priorityLevel: idx + 1 + })) - draggedConfig.value = null; + draggedConfig.value = null } function onSwitchChange(val) { console.log('📌 เปลี่ยนค่า isProductionSpare เป็น:', stockConfigStore.isProductionSpare) } +// ป้องกันไม่ให้พิมพ์ตัวอักษรหรือสัญลักษณ์ที่ไม่ต้องการ +function blockInvalidKeys(event) { + const invalidKeys = ['e', 'E', '+', '-', '.', ','] + if (invalidKeys.includes(event.key)) { + event.preventDefault() + } +} + +// ฟังก์ชันสำหรับบันทึกการเปลี่ยนแปลง targetStockLevel เมื่อกด Enter +async function saveTargetStockLevel(config) { + const id = config.StockConfigID + const newValue = Number(config.targetStockLevel) + if (isNaN(newValue) || newValue < 0) return + if (savingRowIds.value.includes(id)) return + + savingRowIds.value.push(id) + + try { + // สมมติว่ามีเมธอดใน store สำหรับแก้ไขข้อมูลแต่ละรายการ + await stockConfigStore.editStockConfig(config.StockConfigID, { + targetStockLevel: config.targetStockLevel, + }) + + // ✅ รีโหลดข้อมูลใหม่ + await stockConfigStore.fetchStockConfigs() + recentlySavedIds.value.push(id) + setTimeout(() => { + recentlySavedIds.value = recentlySavedIds.value.filter((rowId) => rowId !== id) + }, 1000) + } catch (e) { + console.error('❌ Save failed:', e) + } finally { + savingRowIds.value = savingRowIds.value.filter((rowId) => rowId !== id) + } +} </script> +<template> + <v-card class="pa-5 dialog-card"> + <p class="text-center font-weight-bold mb-4 text-white" style="font-size: 20px"> + หน้าจัดแต่งการเก็บ + </p> + + <v-table dense class="product-table"> + <thead> + <tr> + <th>ลำดับความสำคัญ</th> + <th>ชื่อ</th> + <th>ชนิด</th> + <th>แบรนด์</th> + <th>จำนวนที่ต้องการเก็บ</th> + <th>หน่วย</th> + </tr> + </thead> + <transition-group tag="tbody" name="list"> + <tr + v-for="config in stockConfigStore.stockConfigs" + :key="config.StockConfigID" + draggable="true" + @dragstart="() => handleDragStart(config)" + @dragover="handleDragOver" + @drop="() => handleDrop(config)" + > + <td class="drag-handle">☰</td> + <td>{{ config.itemName }}</td> + <td>{{ config.itemType }}</td> + <td>{{ config.itemDetail?.brand ?? '-' }}</td> + <td> + <div style="position: relative; display: inline-block"> + <v-text-field + class="tiny-input" + v-model.number="config.targetStockLevel" + type="number" + density="compact" + variant="plain" + hide-details + style="max-width: 80px" + min="0" + step="1" + :loading="savingRowIds.includes(config.StockConfigID)" + @keydown="blockInvalidKeys" + @keyup.enter="saveTargetStockLevel(config)" + /> + <v-icon + v-if="recentlySavedIds.includes(config.StockConfigID)" + color="green" + class="fade-check" + style="position: absolute; right: -24px; top: 4px" + > + mdi-check-circle + </v-icon> + </div> + </td> + <td>{{ config.itemDetail?.unit ?? '-' }}</td> + </tr> + </transition-group> + </v-table> + + <!-- Toggle & Action Buttons --> + <div class="action-section"> + <v-switch + color="primary" + v-model="stockConfigStore.isProductionSpare" + :label="stockConfigStore.productionSpareLabel" + class="switch-toggle" + @change="onSwitchChange" + /> + </div> + </v-card> +</template> + <style scoped> .dialog-card { background-color: #2b2e3f; @@ -143,19 +213,23 @@ function onSwitchChange(val) { /* Animation สำหรับ Transition Group */ .list-move { - transition: transform 0.3s ease, opacity 0.3s ease; + transition: + transform 0.3s ease, + opacity 0.3s ease; } .list-enter-active, .list-leave-active { - transition: opacity 0.3s, transform 0.3s; + transition: + opacity 0.3s, + transform 0.3s; } .list-enter-from, .list-leave-to { opacity: 0; transform: translateY(-10px); -} +}w .switch-toggle { color: white; @@ -167,4 +241,24 @@ function onSwitchChange(val) { align-items: center; margin-top: 15px; } + +/* Animation เมื่อบันทึกเสร็จ */ +.fade-check { + animation: fadeOut 1s ease-out forwards; +} + +@keyframes fadeOut { + 0% { + opacity: 1; + transform: scale(1); + } + 80% { + opacity: 1; + transform: scale(1.2); + } + 100% { + opacity: 0; + transform: scale(1); + } +} </style> diff --git a/src/services/stockConfig.ts b/src/services/stockConfig.ts index e82fd6ff573e0d0f3b8cf46b478a2764a2787666..e738d23c2480d796ad8f3d1f8824b09ab1ddec38 100644 --- a/src/services/stockConfig.ts +++ b/src/services/stockConfig.ts @@ -36,7 +36,7 @@ export async function createStockConfig(payload: CreateStockConfigDto): Promise< * อัปเดต StockConfig */ export async function updateStockConfig(id: number, payload: UpdateStockConfigDto): Promise<StockConfig> { - const { data } = await http.put(`/stock-config/${id}`, payload); + const { data } = await http.patch(`/stock-config/${id}`, payload); // ✅ เปลี่ยนเป็น patch return data; }