diff --git a/src/app/(main)/finishedGood/detail/page.tsx b/src/app/(main)/finishedGood/detail/page.tsx index b3d616d..058ad77 100644 --- a/src/app/(main)/finishedGood/detail/page.tsx +++ b/src/app/(main)/finishedGood/detail/page.tsx @@ -18,7 +18,7 @@ const PickOrder: React.FC = async ({ searchParams }) => { return ( <> - + }> diff --git a/src/app/(main)/finishedGood/page.tsx b/src/app/(main)/finishedGood/page.tsx index ab7ef0f..843f02c 100644 --- a/src/app/(main)/finishedGood/page.tsx +++ b/src/app/(main)/finishedGood/page.tsx @@ -17,7 +17,7 @@ const PickOrder: React.FC = async () => { return ( <> - + }> diff --git a/src/app/api/do/actions.tsx b/src/app/api/do/actions.tsx index a385dcc..f3be747 100644 --- a/src/app/api/do/actions.tsx +++ b/src/app/api/do/actions.tsx @@ -3,7 +3,7 @@ import { BASE_API_URL } from "@/config/api"; // import { ServerFetchError, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; import { revalidateTag } from "next/cache"; import { cache } from "react"; -import { serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; +import { serverFetch, serverFetchJson, serverFetchWithNoContent } from "@/app/utils/fetchUtil"; import { QcItemResult } from "../settings/qcItem"; import { RecordsRes } from "../utils"; import { DoResult } from "."; @@ -129,11 +129,22 @@ export interface getTicketReleaseTable { shopName: string | null; requiredDeliveryDate: string | null; handlerName: string | null; + numberOfFGItems: number; } -export const fetchTicketReleaseTable = cache(async ()=> { +export const fetchTicketReleaseTable = cache(async (startDate?: string, endDate?: string) => { + const params = new URLSearchParams(); + if (startDate) { + params.append('startDate', startDate); + } + if (endDate) { + params.append('endDate', endDate); + } + + const url = `${BASE_API_URL}/doPickOrder/ticket-release-table${params.toString() ? `?${params.toString()}` : ''}`; + return await serverFetchJson( - `${BASE_API_URL}/doPickOrder/ticket-release-table`, + url, { method: "GET", } @@ -226,11 +237,29 @@ export async function printDN(request: PrintDeliveryNoteRequest){ params.append('numOfCarton', request.numOfCarton.toString()); params.append('isDraft', request.isDraft.toString()); - const response = await serverFetchWithNoContent(`${BASE_API_URL}/do/print-DN?${params.toString()}`,{ - method: "GET", - }); - - return { success: true, message: "Print job sent successfully (DN)" } as PrintDeliveryNoteResponse; + try { + const response = await serverFetch(`${BASE_API_URL}/do/print-DN?${params.toString()}`, { + method: "GET", + }); + + if (response.ok) { + return { success: true, message: "Print job sent successfully (DN)" } as PrintDeliveryNoteResponse; + } + + const errorText = await response.text(); + console.error("Print DN error:", errorText); + return { + success: false, + message: "No data found for this pick order." + } as PrintDeliveryNoteResponse; + + } catch (error) { + console.error("Error in printDN:", error); + return { + success: false, + message: "No data found for this pick order." + } as PrintDeliveryNoteResponse; + } } export async function printDNLabels(request: PrintDNLabelsRequest){ diff --git a/src/app/api/pickOrder/actions.ts b/src/app/api/pickOrder/actions.ts index 50d6a92..7e1ee51 100644 --- a/src/app/api/pickOrder/actions.ts +++ b/src/app/api/pickOrder/actions.ts @@ -357,6 +357,7 @@ export interface CompletedDoPickOrderResponse { storeId: string; completedDate: string; fgPickOrders: FGPickOrderResponse[]; + deliveryNoteCode: number; } // 新增:搜索参数接口 diff --git a/src/components/FinishedGoodSearch/FGPickOrderTicketReleaseTable.tsx b/src/components/FinishedGoodSearch/FGPickOrderTicketReleaseTable.tsx index a52a238..df6e0b3 100644 --- a/src/components/FinishedGoodSearch/FGPickOrderTicketReleaseTable.tsx +++ b/src/components/FinishedGoodSearch/FGPickOrderTicketReleaseTable.tsx @@ -24,13 +24,15 @@ import { } from '@mui/material'; import { useTranslation } from 'react-i18next'; import dayjs from 'dayjs'; +import { arrayToDayjs } from '@/app/utils/formatUtil'; import { fetchTicketReleaseTable, getTicketReleaseTable } from '@/app/api/do/actions'; -import { time } from 'console'; const FGPickOrderTicketReleaseTable: React.FC = () => { - const { t } = useTranslation("pickOrder"); + const { t } = useTranslation("ticketReleaseTable"); const [selectedDate, setSelectedDate] = useState("today"); const [selectedFloor, setSelectedFloor] = useState(""); + const [selectedStatus, setSelectedStatus] = useState("released"); + const [data, setData] = useState([]); const [loading, setLoading] = useState(true); const [paginationController, setPaginationController] = useState({ @@ -62,16 +64,22 @@ const FGPickOrderTicketReleaseTable: React.FC = () => { const formattedMinute = minute.toString().padStart(2, '0'); return `${formattedHour}:${formattedMinute}`; }; - const getDateLabel = (offset: number) => { return dayjs().add(offset, 'day').format('YYYY-MM-DD'); }; + const getDateRange = () => { + const today = dayjs().format('YYYY-MM-DD'); + const dayAfterTomorrow = dayjs().add(2, 'day').format('YYYY-MM-DD'); + return { startDate: today, endDate: dayAfterTomorrow }; + }; + useEffect(() => { const loadData = async () => { setLoading(true); try { - const result = await fetchTicketReleaseTable(); + const { startDate, endDate } = getDateRange(); + const result = await fetchTicketReleaseTable(startDate, endDate); setData(result); } catch (error) { console.error('Error fetching ticket release table:', error); @@ -99,8 +107,14 @@ const FGPickOrderTicketReleaseTable: React.FC = () => { } } + // Filter by status if selected + if (selectedStatus && item.ticketStatus?.toLowerCase() !== selectedStatus.toLowerCase()) { + return false; + } + + return true; - },[data, selectedDate, selectedFloor]); + },[data, selectedDate, selectedFloor, selectedStatus]); const handlePageChange = useCallback((event: unknown, newPage: number) => { setPaginationController(prev => ({ @@ -125,19 +139,18 @@ const FGPickOrderTicketReleaseTable: React.FC = () => { useEffect(() => { setPaginationController(prev => ({ ...prev, pageNum: 0 })); - }, [selectedDate, selectedFloor]); + }, [selectedDate, selectedFloor, selectedStatus]); return ( {/* Title */} - Ticket Release Table + {t("Ticket Release Table")} {/* Dropdown Menus */} - {/* Date Selection Dropdown */} {t("Select Date")} + + + + {t("Status")} + + + @@ -202,13 +235,13 @@ const FGPickOrderTicketReleaseTable: React.FC = () => { {t("Truck Information")} - {t("Departure Time")} {t("Truck Lane Code")} + {t("Truck Lane Code")} - {t("Departure Time")} {/*{t("Truck Departure Time")} {t("Truck Lane Code")}*/} - {t("Shop Name")} + {t("Shop Name")} {t("Loading Sequence")} {/*{t("Delivery Order Code(s)")} {t("Pick Order Code(s)")} @@ -230,7 +263,7 @@ const FGPickOrderTicketReleaseTable: React.FC = () => { {t("Handler Name")} - {t("Number of FG Items")} + {t("Number of FG Items (Order Item(s) Count)")} @@ -275,7 +308,7 @@ const FGPickOrderTicketReleaseTable: React.FC = () => { - {row.shopName || '-'} + {row.shopName || '-'} {row.loadingSequence || '-'} {/*{row.deliveryOrderCode || '-'} {row.pickOrderCode || '-'} @@ -295,22 +328,40 @@ const FGPickOrderTicketReleaseTable: React.FC = () => { - {row.ticketNo || '-'} ({row.ticketStatus || '-'}) + {row.ticketNo || '-'} ({row.ticketStatus ? t(row.ticketStatus.toLowerCase()) : '-'}) - {row.ticketReleaseTime - ? dayjs(row.ticketReleaseTime, 'YYYYMMDDHHmmss').format('YYYY-MM-DD HH:mm') + {t("Released Time")}: {row.ticketReleaseTime + ? (() => { + if (Array.isArray(row.ticketReleaseTime)) { + return arrayToDayjs(row.ticketReleaseTime, true).format('HH:mm'); + } + const parsedDate = dayjs(row.ticketReleaseTime, 'YYYYMMDDHHmmss'); + if (!parsedDate.isValid()) { + return dayjs(row.ticketReleaseTime).format('HH:mm'); + } + return parsedDate.format('HH:mm'); + })() : '-'} - {row.ticketCompleteDateTime - ? dayjs(row.ticketCompleteDateTime, 'YYYYMMDDHHmmss').format('YYYY-MM-DD HH:mm') + {t("Completed Time")}: {row.ticketCompleteDateTime + ? (() => { + if (Array.isArray(row.ticketCompleteDateTime)) { + return arrayToDayjs(row.ticketCompleteDateTime, true).format('HH:mm'); + } + const parsedDate = dayjs(row.ticketCompleteDateTime, 'YYYYMMDDHHmmss'); + if (!parsedDate.isValid()) { + return dayjs(row.ticketCompleteDateTime).format('HH:mm'); + } + return parsedDate.format('HH:mm'); + })() : '-'} {row.handlerName || '-'} - - + {row.numberOfFGItems ?? 0} ); }) diff --git a/src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx b/src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx index 941aa96..5207cd8 100644 --- a/src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx +++ b/src/components/FinishedGoodSearch/FinishedGoodFloorLanePanel.tsx @@ -140,36 +140,54 @@ const FinishedGoodFloorLanePanel: React.FC = ({ onPickOrderAssigned, onSw return ( - {/* Date Selector Dropdown */} - - - {t("Select Date")} - - { { + setSelectedDate(e.target.value); + loadSummaries(); + }}} + > + + + {t("Today")} ({getDateLabel(0)}) + + + {t("Tomorrow")} ({getDateLabel(1)}) + + + {t("Day After Tomorrow")} ({getDateLabel(2)}) + + + + + + + + + {t("EDT - Lane Code (Unassigned/Total)")} + + + - - {t("Today")} ({getDateLabel(0)}) - - - {t("Tomorrow")} ({getDateLabel(1)}) - - - {t("Day After Tomorrow")} ({getDateLabel(2)}) - - - - - {/* Grid containing both floors */} @@ -299,7 +317,7 @@ const FinishedGoodFloorLanePanel: React.FC = ({ onPickOrderAssigned, onSw }} > {isLoadingSummary ? ( - Loading... + {t("Loading...")} ) : !summary4F?.rows || summary4F.rows.length === 0 ? ( = ({ pickOrders }) => { }); } else { console.error("Print failed: ", response.message); + Swal.fire({ + title: "", + text: t("Please take one pick order before printing the draft."), + icon: "info" + }) } } catch(error){ console.error("error: ", error) @@ -247,7 +252,7 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => { const handleCompletionStatusChange = (event: CustomEvent) => { const { allLotsCompleted, tabIndex: eventTabIndex } = event.detail; - // 修复:根据标签页和事件来源决定是否更新打印按钮状态 + // ✅ 修复:根据标签页和事件来源决定是否更新打印按钮状态 if (eventTabIndex === undefined || eventTabIndex === tabIndex) { setPrintButtonsEnabled(allLotsCompleted); console.log(`Print buttons enabled for tab ${tabIndex}:`, allLotsCompleted); @@ -259,9 +264,9 @@ const PickOrderSearch: React.FC = ({ pickOrders }) => { return () => { window.removeEventListener('pickOrderCompletionStatus', handleCompletionStatusChange as EventListener); }; - }, [tabIndex]); // 添加 tabIndex 依赖 + }, [tabIndex]); // ✅ 添加 tabIndex 依赖 - // 新增:处理标签页切换时的打印按钮状态重置 + // ✅ 新增:处理标签页切换时的打印按钮状态重置 useEffect(() => { // 当切换到标签页 2 (GoodPickExecutionRecord) 时,重置打印按钮状态 if (tabIndex === 2) { @@ -286,7 +291,7 @@ const handleAssignByLane = useCallback(async ( const res = await assignByLane(currentUserId, storeId, truckLanceCode, truckDepartureTime); if (res.code === "SUCCESS") { - console.log(" Successfully assigned pick order from lane", truckLanceCode); + console.log("✅ Successfully assigned pick order from lane", truckLanceCode); window.dispatchEvent(new CustomEvent('pickOrderAssigned')); loadSummaries(); // 刷新按钮状态 } else if (res.code === "USER_BUSY") { @@ -322,7 +327,7 @@ const handleAssignByLane = useCallback(async ( setIsAssigning(false); } }, [currentUserId, t, loadSummaries]); - // Manual assignment handler - uses the action function + // ✅ Manual assignment handler - uses the action function */ const handleTabChange = useCallback>( (_e, newValue) => { @@ -334,6 +339,10 @@ const handleAssignByLane = useCallback(async ( const handleSwitchToDetailTab = useCallback(() => { setTabIndex(1); }, []); + + const handleSwtitchToRecordTab = useCallback(() =>{ + setTabIndex(2); + }, []); const openCreateModal = useCallback(async () => { console.log("testing") @@ -611,7 +620,7 @@ const handleAssignByLane = useCallback(async ( - {/* Tabs section - Move the click handler here */} + {/* Tabs section - ✅ Move the click handler here */} @@ -634,7 +643,13 @@ const handleAssignByLane = useCallback(async ( onSwitchToDetailTab={handleSwitchToDetailTab} /> )} - {tabIndex === 1 && } + {tabIndex === 1 && ( + + ) } {tabIndex === 2 && } {tabIndex === 3 && } diff --git a/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx b/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx index 1e7046a..068f094 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx @@ -190,7 +190,11 @@ const validateForm = (): boolean => { } // 2. 检查 actualPickQty 不能超过可用数量或需求数量 +<<<<<<< Updated upstream if (ap > Math.min( req)) { +======= + if (ap > Math.min(req)) { +>>>>>>> Stashed changes newErrors.actualPickQty = t('Qty is not allowed to be greater than required/available qty'); } diff --git a/src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx b/src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx index c5981f9..510546a 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutionRecord.tsx @@ -564,33 +564,20 @@ if (showDetailView && selectedDoPickOrder) { {t("Completed Date")}: {dayjs(selectedDoPickOrder.completedDate).format(OUTPUT_DATE_FORMAT)} + {selectedDoPickOrder.pickOrderIds && selectedDoPickOrder.pickOrderIds.length > 1 && ( + + {t("Pick Order Code(s)")}:{" "} + {(typeof selectedDoPickOrder.pickOrderCodes === 'string' + ? selectedDoPickOrder.pickOrderCodes.split(',').map(code => code.trim()) + : Array.isArray(selectedDoPickOrder.pickOrderCodes) + ? selectedDoPickOrder.pickOrderCodes + : [] + ).filter(Boolean).join(', ')} + + )} - {/* 添加:多个 Pick Orders 信息(如果有) */} - {selectedDoPickOrder.pickOrderIds && selectedDoPickOrder.pickOrderIds.length > 0 && ( - - - {t("This ticket contains")} {selectedDoPickOrder.pickOrderIds.length} {t("pick orders")}: - - - {(typeof selectedDoPickOrder.pickOrderCodes === 'string' - ? selectedDoPickOrder.pickOrderCodes.split(',').map(code => code.trim()) - : Array.isArray(selectedDoPickOrder.pickOrderCodes) - ? selectedDoPickOrder.pickOrderCodes - : [] - ).filter(Boolean).map((code, idx) => ( - - ))} - - - )} - {/* 数据检查 */} {detailLotData.length === 0 ? ( @@ -710,11 +697,11 @@ if (showDetailView && selectedDoPickOrder) { - {doPickOrder.pickOrderCode} + {doPickOrder.deliveryNoteCode} - {doPickOrder.shopName} - {doPickOrder.deliveryNo} + {doPickOrder.shopName} {t("Completed")}: {dayjs(doPickOrder.completedDate).format(OUTPUT_DATE_FORMAT)} diff --git a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx index c4b5e3a..b2f43bb 100644 --- a/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx +++ b/src/components/FinishedGoodSearch/GoodPickExecutiondetail.tsx @@ -68,6 +68,8 @@ import GoodPickExecutionForm from "./GoodPickExecutionForm"; import FGPickOrderCard from "./FGPickOrderCard"; interface Props { filterArgs: Record; + onSwitchToRecordTab?: () => void; + onRefreshReleasedOrderCount?: () => void; } // QR Code Modal Component (from LotTable) @@ -324,7 +326,7 @@ const QrCodeModal: React.FC<{ ); }; -const PickExecution: React.FC = ({ filterArgs }) => { +const PickExecution: React.FC = ({ filterArgs, onSwitchToRecordTab, onRefreshReleasedOrderCount }) => { const { t } = useTranslation("pickOrder"); const router = useRouter(); const { data: session } = useSession() as { data: SessionWithTokens | null }; @@ -579,7 +581,7 @@ console.log("🔍 DEBUG fgOrder.deliveryNos:", fgOrder.deliveryNos); pickOrderTargetDate: mergedPickOrder.targetDate, pickOrderStatus: mergedPickOrder.status, pickOrderId: line.pickOrderId || mergedPickOrder.pickOrderIds?.[0] || 0, // 使用第一个 pickOrderId - pickOrderCode: mergedPickOrder.pickOrderCodes?.[0] || "", + pickOrderCode: mergedPickOrder.pickOrderCodes?.[0] || "", pickOrderLineId: line.id, pickOrderLineRequiredQty: line.requiredQty, pickOrderLineStatus: line.status, @@ -1678,6 +1680,12 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe setTimeout(() => { setQrScanSuccess(false); checkAndAutoAssignNext(); + if (onSwitchToRecordTab) { + onSwitchToRecordTab(); + } + if (onRefreshReleasedOrderCount) { + onRefreshReleasedOrderCount(); + } }, 2000); } @@ -1687,7 +1695,7 @@ const handleSubmitPickQtyWithQty = useCallback(async (lot: any, submitQty: numbe } finally { setIsSubmittingAll(false); } - }, [combinedLotData, fetchAllCombinedLotData, checkAndAutoAssignNext, handlelotnull]); + }, [combinedLotData, fetchAllCombinedLotData, checkAndAutoAssignNext, handlelotnull, onSwitchToRecordTab, onRefreshReleasedOrderCount]); // Calculate scanned items count // Calculate scanned items count (should match handleSubmitAllScanned filter logic) diff --git a/src/i18n/zh/pickOrder.json b/src/i18n/zh/pickOrder.json index 6c80296..fcebe4f 100644 --- a/src/i18n/zh/pickOrder.json +++ b/src/i18n/zh/pickOrder.json @@ -405,6 +405,7 @@ "Print DN & Label": "列印提料單和送貨單標籤", "Print Label": "列印送貨單標籤", "Ticket Release Table": "查看提貨情況", - "Please take one pick order before printing the draft.": "請先從下方選取提料單,再列印草稿。", - "No released pick order records found.": "目前沒有可用的提料單。" + "Please take one pick order before printing the draft.": "請先從「撳單/提料單詳情」頁面下方選取提料單,再列印草稿。", + "No released pick order records found.": "目前沒有可用的提料單。", + "EDT - Lane Code (Unassigned/Total)": "預計出發時間 - 貨車班次(未撳數/總單數)" } \ No newline at end of file diff --git a/src/i18n/zh/ticketReleaseTable.json b/src/i18n/zh/ticketReleaseTable.json new file mode 100644 index 0000000..ff73de7 --- /dev/null +++ b/src/i18n/zh/ticketReleaseTable.json @@ -0,0 +1,29 @@ +{ + "Ticket Release Table": "查看提貨情況", + "Select Date": "請選擇日期", + "Today": "是日", + "Tomorrow": "翌日", + "Day After Tomorrow": "後日", + "Floor": "樓層", + "All Floors": "所有樓層", + "Store ID": "樓層", + "Required Delivery Date": "需求送貨日期", + "Truck Information": "貨車資訊", + "Departure Time": "出發時間", + "Truck Lane Code": "車綫編號", + "Shop Name": "店鋪名稱", + "Loading Sequence": "裝載順序", + "Ticket Information": "提票資訊", + "Ticket No.": "提票號碼", + "Status": "狀態", + "Released Time": "開始時間", + "Completed Time": "完成時間", + "Handler Name": "負責員工", + "Number of FG Items (Order Item(s) Count)": "訂單項目數量", + "No data available": "沒有資料", + "Rows per page": "每頁行數", + "pending": "待撳單", + "released": "提貨中", + "completed": "已完成", + "All Statuses": "所有提貨狀態" +} \ No newline at end of file