| @@ -443,6 +443,7 @@ export interface UpdatePickExecutionIssueRequest { | |||||
| export interface StoreLaneSummary { | export interface StoreLaneSummary { | ||||
| storeId: string; | storeId: string; | ||||
| rows: LaneRow[]; | rows: LaneRow[]; | ||||
| defaultTruckCount: number | null; | |||||
| } | } | ||||
| export interface LaneRow { | export interface LaneRow { | ||||
| @@ -606,16 +607,22 @@ export const updatePickExecutionIssueStatus = async ( | |||||
| }; | }; | ||||
| export async function fetchStoreLaneSummary(storeId: string, requiredDate?: string, releaseType?: string): Promise<StoreLaneSummary> { | export async function fetchStoreLaneSummary(storeId: string, requiredDate?: string, releaseType?: string): Promise<StoreLaneSummary> { | ||||
| const dateToUse = requiredDate || dayjs().format('YYYY-MM-DD'); | const dateToUse = requiredDate || dayjs().format('YYYY-MM-DD'); | ||||
| const url = `${BASE_API_URL}/doPickOrder/summary-by-store?storeId=${encodeURIComponent(storeId)}&requiredDate=${encodeURIComponent(dateToUse)}&releaseType=${encodeURIComponent(releaseType || 'all')}`; | const url = `${BASE_API_URL}/doPickOrder/summary-by-store?storeId=${encodeURIComponent(storeId)}&requiredDate=${encodeURIComponent(dateToUse)}&releaseType=${encodeURIComponent(releaseType || 'all')}`; | ||||
| const response = await serverFetchJson<StoreLaneSummary>( | |||||
| url, | |||||
| { | |||||
| const label = `[API] fetchStoreLaneSummary ${storeId}`; | |||||
| console.time(label); | |||||
| try { | |||||
| const response = await serverFetchJson<StoreLaneSummary>(url, { | |||||
| method: "GET", | method: "GET", | ||||
| cache: "no-store", | cache: "no-store", | ||||
| next: { revalidate: 0 } | |||||
| } | |||||
| ); | |||||
| return response; | |||||
| next: { revalidate: 0 }, | |||||
| }); | |||||
| console.timeEnd(label); | |||||
| return response; | |||||
| } catch (error) { | |||||
| console.error(`[API] Error in fetchStoreLaneSummary ${storeId}:`, error); | |||||
| throw error; | |||||
| } | |||||
| } | } | ||||
| // 按车道分配订单 | // 按车道分配订单 | ||||
| @@ -1414,6 +1421,22 @@ export const fetchReleasedDoPickOrdersForSelection = async ( | |||||
| }); | }); | ||||
| return response ?? []; | return response ?? []; | ||||
| }; | }; | ||||
| export const fetchReleasedDoPickOrdersForSelectionToday = async ( | |||||
| shopName?: string, | |||||
| storeId?: string, | |||||
| truck?: string | |||||
| ): Promise<ReleasedDoPickOrderListItem[]> => { | |||||
| const params = new URLSearchParams(); | |||||
| if (shopName?.trim()) params.append("shopName", shopName.trim()); | |||||
| if (storeId?.trim()) params.append("storeId", storeId.trim()); | |||||
| if (truck?.trim()) params.append("truck", truck.trim()); | |||||
| const query = params.toString(); | |||||
| const url = `${BASE_API_URL}/doPickOrder/released-today${query ? `?${query}` : ""}`; | |||||
| const response = await serverFetchJson<ReleasedDoPickOrderListItem[]>(url, { | |||||
| method: "GET", | |||||
| }); | |||||
| return response ?? []; | |||||
| }; | |||||
| export const fetchReleasedDoPickOrderCountByStore = async ( | export const fetchReleasedDoPickOrderCountByStore = async ( | ||||
| storeId: string | storeId: string | ||||
| ): Promise<number> => { | ): Promise<number> => { | ||||
| @@ -1,7 +1,7 @@ | |||||
| "use client"; | "use client"; | ||||
| import { Box, Button, Grid, Stack, Typography, Select, MenuItem, FormControl, InputLabel ,Tooltip} from "@mui/material"; | import { Box, Button, Grid, Stack, Typography, Select, MenuItem, FormControl, InputLabel ,Tooltip} from "@mui/material"; | ||||
| import { useCallback, useEffect, useState } from "react"; | |||||
| import { useCallback, useEffect, useState, useRef } from "react"; | |||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import { useSession } from "next-auth/react"; | import { useSession } from "next-auth/react"; | ||||
| import { SessionWithTokens } from "@/config/authConfig"; | import { SessionWithTokens } from "@/config/authConfig"; | ||||
| @@ -21,18 +21,48 @@ const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned, onSw | |||||
| const currentUserId = session?.id ? parseInt(session.id) : undefined; | const currentUserId = session?.id ? parseInt(session.id) : undefined; | ||||
| const [selectedStore, setSelectedStore] = useState<string>("2/F"); | const [selectedStore, setSelectedStore] = useState<string>("2/F"); | ||||
| const [selectedTruck, setSelectedTruck] = useState<string>(""); | const [selectedTruck, setSelectedTruck] = useState<string>(""); | ||||
| const [selectedDefaultTruck, setSelectedDefaultTruck] = useState<string>(""); | |||||
| const [modalOpen, setModalOpen] = useState(false); | const [modalOpen, setModalOpen] = useState(false); | ||||
| const [truckCounts2F, setTruckCounts2F] = useState<{ truck: string; count: number }[]>([]); | const [truckCounts2F, setTruckCounts2F] = useState<{ truck: string; count: number }[]>([]); | ||||
| const [truckCounts4F, setTruckCounts4F] = useState<{ truck: string; count: number }[]>([]); | const [truckCounts4F, setTruckCounts4F] = useState<{ truck: string; count: number }[]>([]); | ||||
| const [summary2F, setSummary2F] = useState<StoreLaneSummary | null>(null); | const [summary2F, setSummary2F] = useState<StoreLaneSummary | null>(null); | ||||
| const [summary4F, setSummary4F] = useState<StoreLaneSummary | null>(null); | const [summary4F, setSummary4F] = useState<StoreLaneSummary | null>(null); | ||||
| // 其他 state 旁邊加一組: | |||||
| const hasLoggedRef = useRef(false); | |||||
| const fullReadyLoggedRef = useRef(false); | |||||
| const pendingRef = useRef(0); | |||||
| const [defaultDateScope, setDefaultDateScope] = useState<"today" | "before">("today"); | |||||
| const [isLoadingSummary, setIsLoadingSummary] = useState(false); | const [isLoadingSummary, setIsLoadingSummary] = useState(false); | ||||
| const [isAssigning, setIsAssigning] = useState(false); | const [isAssigning, setIsAssigning] = useState(false); | ||||
| const [isDefaultTruck, setIsDefaultTruck] = useState(false); | |||||
| //const [selectedDate, setSelectedDate] = useState<string>("today"); | //const [selectedDate, setSelectedDate] = useState<string>("today"); | ||||
| const defaultTruckCount = summary4F?.defaultTruckCount ?? 0; | |||||
| const [beforeTodayTruckXCount, setBeforeTodayTruckXCount] = useState(0); | |||||
| const [selectedDate, setSelectedDate] = useState<string>("today"); | const [selectedDate, setSelectedDate] = useState<string>("today"); | ||||
| const [releaseType, setReleaseType] = useState<string>("batch"); | const [releaseType, setReleaseType] = useState<string>("batch"); | ||||
| const startFullTimer = () => { | |||||
| if (typeof window === "undefined") return; | |||||
| const key = "__FG_FLOOR_FULL_TIMER_STARTED__" as const; | |||||
| if (!(window as any)[key]) { | |||||
| (window as any)[key] = true; | |||||
| console.time("[FG] FloorLanePanel full ready"); | |||||
| } | |||||
| }; | |||||
| const tryEndFullTimer = () => { | |||||
| if (typeof window === "undefined") return; | |||||
| const key = "__FG_FLOOR_FULL_TIMER_STARTED__" as const; | |||||
| if ((window as any)[key] && !fullReadyLoggedRef.current && pendingRef.current === 0) { | |||||
| fullReadyLoggedRef.current = true; | |||||
| console.timeEnd("[FG] FloorLanePanel full ready"); | |||||
| delete (window as any)[key]; | |||||
| } | |||||
| }; | |||||
| const loadSummaries = useCallback(async () => { | const loadSummaries = useCallback(async () => { | ||||
| setIsLoadingSummary(true); | setIsLoadingSummary(true); | ||||
| pendingRef.current += 1; | |||||
| startFullTimer(); | |||||
| try { | try { | ||||
| // Convert selectedDate to the format needed | // Convert selectedDate to the format needed | ||||
| let dateParam: string | undefined; | let dateParam: string | undefined; | ||||
| @@ -54,15 +84,33 @@ const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned, onSw | |||||
| console.error("Error loading summaries:", error); | console.error("Error loading summaries:", error); | ||||
| } finally { | } finally { | ||||
| setIsLoadingSummary(false); | setIsLoadingSummary(false); | ||||
| // ⭐ 新增:this async 结束,pending--,尝试结束 full ready 计时 | |||||
| pendingRef.current -= 1; | |||||
| tryEndFullTimer(); | |||||
| if (!hasLoggedRef.current) { | |||||
| hasLoggedRef.current = true; | |||||
| if (typeof window !== "undefined") { | |||||
| const key = "__FG_FLOOR_PANEL_TIMER_STARTED__" as const; | |||||
| if ((window as any)[key]) { | |||||
| console.timeEnd("[FG] FloorLanePanel initial load"); | |||||
| delete (window as any)[key]; | |||||
| } else { | |||||
| console.log("Timer '[FG] FloorLanePanel initial load' already ended or never started, skip."); | |||||
| } | |||||
| } | |||||
| } | |||||
| } | } | ||||
| }, [selectedDate, releaseType]); | }, [selectedDate, releaseType]); | ||||
| // 初始化 | // 初始化 | ||||
| useEffect(() => { | useEffect(() => { | ||||
| loadSummaries(); | loadSummaries(); | ||||
| }, [loadSummaries]); | }, [loadSummaries]); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| const loadCounts = async () => { | const loadCounts = async () => { | ||||
| pendingRef.current += 1; | |||||
| startFullTimer(); | |||||
| try { | try { | ||||
| const [list2F, list4F] = await Promise.all([ | const [list2F, list4F] = await Promise.all([ | ||||
| fetchReleasedDoPickOrdersForSelection(undefined, "2/F"), | fetchReleasedDoPickOrdersForSelection(undefined, "2/F"), | ||||
| @@ -84,10 +132,36 @@ const FinishedGoodFloorLanePanel: React.FC<Props> = ({ onPickOrderAssigned, onSw | |||||
| console.error("Error loading counts:", e); | console.error("Error loading counts:", e); | ||||
| setTruckCounts2F([]); | setTruckCounts2F([]); | ||||
| setTruckCounts4F([]); | setTruckCounts4F([]); | ||||
| }finally { | |||||
| // ⭐ 新增:结束时 pending--,尝试结束 full ready 计时 | |||||
| pendingRef.current -= 1; | |||||
| tryEndFullTimer(); | |||||
| } | } | ||||
| }; | }; | ||||
| loadCounts(); | loadCounts(); | ||||
| }, [loadSummaries]); | }, [loadSummaries]); | ||||
| useEffect(() => { | |||||
| const loadBeforeTodayTruckX = async () => { | |||||
| pendingRef.current += 1; | |||||
| startFullTimer(); | |||||
| try { | |||||
| const list = await fetchReleasedDoPickOrdersForSelection( | |||||
| undefined, // shopName | |||||
| undefined, // storeId: Truck X 的 store_id 是 null | |||||
| "車線-X" // 只看 Truck X | |||||
| ); | |||||
| setBeforeTodayTruckXCount(list.length); | |||||
| } catch (e) { | |||||
| console.error("Error loading beforeTodayTruckX:", e); | |||||
| setBeforeTodayTruckXCount(0); | |||||
| }finally { | |||||
| // ⭐ 新增:结束时 pending--,尝试结束 full ready 计时 | |||||
| pendingRef.current -= 1; | |||||
| tryEndFullTimer(); | |||||
| } | |||||
| }; | |||||
| loadBeforeTodayTruckX(); | |||||
| }, []); | |||||
| const handleAssignByLane = useCallback(async ( | const handleAssignByLane = useCallback(async ( | ||||
| storeId: string, | storeId: string, | ||||
| truckDepartureTime: string, | truckDepartureTime: string, | ||||
| @@ -299,7 +373,7 @@ const getDateLabel = (offset: number) => { | |||||
| {/* Grid containing both floors */} | {/* Grid containing both floors */} | ||||
| <Grid container spacing={2}> | |||||
| <Grid container spacing={2}> | |||||
| {/* 2/F 楼层面板 */} | {/* 2/F 楼层面板 */} | ||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| <Stack direction="row" spacing={2} alignItems="flex-start"> | <Stack direction="row" spacing={2} alignItems="flex-start"> | ||||
| @@ -499,6 +573,45 @@ const getDateLabel = (offset: number) => { | |||||
| </Box> | </Box> | ||||
| </Stack> | </Stack> | ||||
| </Grid> | </Grid> | ||||
| {/* 4/F Today default lane*/} | |||||
| <Grid item xs={12}> | |||||
| <Stack direction="row" spacing={2} alignItems="flex-start"> | |||||
| <Typography sx={{ fontWeight: 600, minWidth: 60, pt: 1 }}>{t("Truck X")}</Typography> | |||||
| <Box | |||||
| sx={{ | |||||
| border: '1px solid #e0e0e0', | |||||
| borderRadius: 1, | |||||
| p: 1, | |||||
| backgroundColor: '#fafafa', | |||||
| flex: 1 | |||||
| }} | |||||
| > | |||||
| {defaultTruckCount === 0 ? ( | |||||
| <Typography >{t("No entries available")}</Typography> | |||||
| ) : ( | |||||
| <Button | |||||
| variant="outlined" | |||||
| size="medium" | |||||
| onClick={() => { | |||||
| // Truck X 綁 4/F,如果你要放在 4/F 區塊 | |||||
| setSelectedStore(""); | |||||
| // 真正的 Truck lane code:車線-X | |||||
| setSelectedTruck("車線-X"); | |||||
| // 告訴 modal 這是 default truck 模式 | |||||
| setIsDefaultTruck(true); | |||||
| // 打開 modal | |||||
| setModalOpen(true); | |||||
| setDefaultDateScope("today"); | |||||
| }} | |||||
| > | |||||
| {`${t("Truck X")} (${defaultTruckCount})`} | |||||
| </Button> | |||||
| )} | |||||
| </Box> | |||||
| </Stack> | |||||
| </Grid> | |||||
| {/* 2/F 未完成已放單 - 與上方相同 UI */} | {/* 2/F 未完成已放單 - 與上方相同 UI */} | ||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| <Box | <Box | ||||
| @@ -652,10 +765,43 @@ const getDateLabel = (offset: number) => { | |||||
| </Box> | </Box> | ||||
| </Stack> | </Stack> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={12}> | |||||
| <Stack direction="row" spacing={2}> | |||||
| <Typography sx={{ fontWeight: 600, minWidth: 60, pt: 1 }}>{t("Truck X")} </Typography> | |||||
| <Box | |||||
| sx={{ | |||||
| border: '1px solid #e0e0e0', | |||||
| borderRadius: 1, | |||||
| p: 1, | |||||
| backgroundColor: '#fafafa', | |||||
| flex: 1 | |||||
| }} | |||||
| > | |||||
| {beforeTodayTruckXCount === 0 ? ( | |||||
| <Typography>{t("No entries available")}</Typography> | |||||
| ) : ( | |||||
| <Button | |||||
| variant="outlined" | |||||
| size="medium" | |||||
| onClick={() => { | |||||
| setSelectedStore("4/F"); // 或用專門標示 Truck X | |||||
| setSelectedTruck("車線-X"); | |||||
| setIsDefaultTruck(true); | |||||
| setDefaultDateScope("before"); // 類似上一輪說的 dateScope | |||||
| setModalOpen(true); | |||||
| }} | |||||
| > | |||||
| {`${t("Truck X")} (${beforeTodayTruckXCount})`} | |||||
| </Button> | |||||
| )} | |||||
| </Box> | |||||
| </Stack> | |||||
| </Grid> | |||||
| <ReleasedDoPickOrderSelectModal | <ReleasedDoPickOrderSelectModal | ||||
| open={modalOpen} | open={modalOpen} | ||||
| storeId={selectedStore} | storeId={selectedStore} | ||||
| truck={selectedTruck} | truck={selectedTruck} | ||||
| isDefaultTruck={isDefaultTruck} | |||||
| onClose={() => setModalOpen(false)} | onClose={() => setModalOpen(false)} | ||||
| onAssigned={() => { | onAssigned={() => { | ||||
| loadSummaries(); | loadSummaries(); | ||||
| @@ -82,8 +82,27 @@ const [selectedPrinterForDraft, setSelectedPrinterForDraft] = useState<PrinterCo | |||||
| ); | ); | ||||
| const [fgPickOrdersData, setFgPickOrdersData] = useState<FGPickOrderResponse[]>([]); | const [fgPickOrdersData, setFgPickOrdersData] = useState<FGPickOrderResponse[]>([]); | ||||
| useEffect(() => { | |||||
| if (typeof window === "undefined") return; | |||||
| const key = "__FG_PAGE_READY_TIMER_STARTED__" as const; | |||||
| if ((window as any)[key]) { | |||||
| console.log("Timer '[FinishedGoodSearch] page ready' already started, skip."); | |||||
| return; | |||||
| } | |||||
| (window as any)[key] = true; | |||||
| console.time("[FinishedGoodSearch] page ready"); | |||||
| }, []); | |||||
| const [releasedOrderCount, setReleasedOrderCount] = useState<number>(0); | const [releasedOrderCount, setReleasedOrderCount] = useState<number>(0); | ||||
| useEffect(() => { | |||||
| console.time("[FinishedGoodSearch] initial render"); | |||||
| return () => { | |||||
| console.timeEnd("[FinishedGoodSearch] initial render"); | |||||
| }; | |||||
| }, []); | |||||
| const fetchReleasedOrderCount = useCallback(async () => { | const fetchReleasedOrderCount = useCallback(async () => { | ||||
| try { | try { | ||||
| const releasedOrders = await fetchReleasedDoPickOrders(); | const releasedOrders = await fetchReleasedDoPickOrders(); | ||||
| @@ -702,6 +721,18 @@ const handleAssignByLane = useCallback(async ( | |||||
| filterArgs={filterArgs} | filterArgs={filterArgs} | ||||
| onFgPickOrdersChange={setFgPickOrdersData} | onFgPickOrdersChange={setFgPickOrdersData} | ||||
| onSwitchToDetailTab={handleSwitchToDetailTab} | onSwitchToDetailTab={handleSwitchToDetailTab} | ||||
| onFirstLoadDone={() => { | |||||
| if (typeof window === "undefined") return; | |||||
| const key = "__FG_PAGE_READY_TIMER_STARTED__" as const; | |||||
| // 只在计时器真的存在时才调用 timeEnd,避免 "does not exist" | |||||
| if ((window as any)[key]) { | |||||
| console.timeEnd("[FinishedGoodSearch] page ready"); | |||||
| delete (window as any)[key]; | |||||
| } else { | |||||
| console.log("Timer '[FinishedGoodSearch] page ready' was already ended, skip."); | |||||
| } | |||||
| }} | |||||
| /> | /> | ||||
| )} | )} | ||||
| {tabIndex === 1 && ( | {tabIndex === 1 && ( | ||||
| @@ -59,6 +59,7 @@ interface Props { | |||||
| filterArgs: Record<string, any>; | filterArgs: Record<string, any>; | ||||
| onFgPickOrdersChange?: (fgPickOrders: FGPickOrderResponse[]) => void; | onFgPickOrdersChange?: (fgPickOrders: FGPickOrderResponse[]) => void; | ||||
| onSwitchToDetailTab?: () => void; | onSwitchToDetailTab?: () => void; | ||||
| onFirstLoadDone?: () => void; | |||||
| } | } | ||||
| // QR Code Modal Component (from LotTable) | // QR Code Modal Component (from LotTable) | ||||
| @@ -72,7 +73,7 @@ const QrCodeModal: React.FC<{ | |||||
| const { t } = useTranslation("pickOrder"); | const { t } = useTranslation("pickOrder"); | ||||
| const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext(); | const { values: qrValues, isScanning, startScan, stopScan, resetScan } = useQrCodeScannerContext(); | ||||
| const [manualInput, setManualInput] = useState<string>(''); | const [manualInput, setManualInput] = useState<string>(''); | ||||
| const floorPanelTimerStartedRef = useRef(false); | |||||
| const [manualInputSubmitted, setManualInputSubmitted] = useState<boolean>(false); | const [manualInputSubmitted, setManualInputSubmitted] = useState<boolean>(false); | ||||
| const [manualInputError, setManualInputError] = useState<boolean>(false); | const [manualInputError, setManualInputError] = useState<boolean>(false); | ||||
| const [isProcessingQr, setIsProcessingQr] = useState<boolean>(false); | const [isProcessingQr, setIsProcessingQr] = useState<boolean>(false); | ||||
| @@ -315,7 +316,7 @@ const QrCodeModal: React.FC<{ | |||||
| ); | ); | ||||
| }; | }; | ||||
| const PickExecution: React.FC<Props> = ({ filterArgs, onFgPickOrdersChange, onSwitchToDetailTab }) => { | |||||
| const PickExecution: React.FC<Props> = ({ filterArgs, onFgPickOrdersChange, onSwitchToDetailTab, onFirstLoadDone }) => { | |||||
| const { t } = useTranslation("pickOrder"); | const { t } = useTranslation("pickOrder"); | ||||
| const router = useRouter(); | const router = useRouter(); | ||||
| const { data: session } = useSession() as { data: SessionWithTokens | null }; | const { data: session } = useSession() as { data: SessionWithTokens | null }; | ||||
| @@ -353,7 +354,7 @@ const [pickOrderSwitching, setPickOrderSwitching] = useState(false); | |||||
| // Add QR modal states | // Add QR modal states | ||||
| const [qrModalOpen, setQrModalOpen] = useState(false); | const [qrModalOpen, setQrModalOpen] = useState(false); | ||||
| const [selectedLotForQr, setSelectedLotForQr] = useState<any | null>(null); | const [selectedLotForQr, setSelectedLotForQr] = useState<any | null>(null); | ||||
| const floorPanelTimerStartedRef = useRef(false); | |||||
| // Add GoodPickExecutionForm states | // Add GoodPickExecutionForm states | ||||
| const [pickExecutionFormOpen, setPickExecutionFormOpen] = useState(false); | const [pickExecutionFormOpen, setPickExecutionFormOpen] = useState(false); | ||||
| const [selectedLotForExecutionForm, setSelectedLotForExecutionForm] = useState<any | null>(null); | const [selectedLotForExecutionForm, setSelectedLotForExecutionForm] = useState<any | null>(null); | ||||
| @@ -523,12 +524,18 @@ const fetchFgPickOrdersData = useCallback(async () => { | |||||
| if (session && currentUserId && !initializationRef.current) { | if (session && currentUserId && !initializationRef.current) { | ||||
| console.log(" Session loaded, initializing pick order..."); | console.log(" Session loaded, initializing pick order..."); | ||||
| initializationRef.current = true; | initializationRef.current = true; | ||||
| // Only fetch existing data, no auto-assignment | |||||
| fetchAllCombinedLotData(); | |||||
| (async () => { | |||||
| try { | |||||
| await fetchAllCombinedLotData(); // ✅ 等待数据加载完成 | |||||
| } finally { | |||||
| if (onFirstLoadDone) { | |||||
| onFirstLoadDone(); // ✅ 这时候再结束 [FinishedGoodSearch] page ready | |||||
| } | |||||
| } | |||||
| })(); | |||||
| } | } | ||||
| }, [session, currentUserId, fetchAllCombinedLotData]); | |||||
| }, [session, currentUserId, fetchAllCombinedLotData, onFirstLoadDone]); | |||||
| // Add event listener for manual assignment | // Add event listener for manual assignment | ||||
| useEffect(() => { | useEffect(() => { | ||||
| const handlePickOrderAssigned = () => { | const handlePickOrderAssigned = () => { | ||||
| @@ -823,7 +830,24 @@ const fetchFgPickOrdersData = useCallback(async () => { | |||||
| } | } | ||||
| }, [qrValues, combinedLotData, handleQrCodeSubmit]); | }, [qrValues, combinedLotData, handleQrCodeSubmit]); | ||||
| useEffect(() => { | |||||
| if (typeof window === "undefined") return; | |||||
| const key = "__FG_FLOOR_PANEL_TIMER_STARTED__" as const; | |||||
| // 只有当「没有 FG 订单」时才会显示 FinishedGoodFloorLanePanel | |||||
| if (fgPickOrders.length === 0 && !fgPickOrdersLoading) { | |||||
| if (!(window as any)[key]) { | |||||
| (window as any)[key] = true; | |||||
| console.time("[FG] FloorLanePanel initial load"); | |||||
| } | |||||
| } | |||||
| // 如果之后拿到 FG 订单,你可以选择在这里清掉标记(可选) | |||||
| if (fgPickOrders.length > 0 && (window as any)[key]) { | |||||
| // delete (window as any)[key]; | |||||
| } | |||||
| }, [fgPickOrders.length, fgPickOrdersLoading]); | |||||
| const handlePickQtyChange = useCallback((lotKey: string, value: number | string) => { | const handlePickQtyChange = useCallback((lotKey: string, value: number | string) => { | ||||
| if (value === '' || value === null || value === undefined) { | if (value === '' || value === null || value === undefined) { | ||||
| setPickQtyData(prev => ({ | setPickQtyData(prev => ({ | ||||
| @@ -22,18 +22,21 @@ import { | |||||
| fetchReleasedDoPickOrdersForSelection, | fetchReleasedDoPickOrdersForSelection, | ||||
| assignByDoPickOrderId, | assignByDoPickOrderId, | ||||
| type ReleasedDoPickOrderListItem, | type ReleasedDoPickOrderListItem, | ||||
| fetchReleasedDoPickOrdersForSelectionToday, | |||||
| } from "@/app/api/pickOrder/actions"; | } from "@/app/api/pickOrder/actions"; | ||||
| import { useSession } from "next-auth/react"; | import { useSession } from "next-auth/react"; | ||||
| import { SessionWithTokens } from "@/config/authConfig"; | import { SessionWithTokens } from "@/config/authConfig"; | ||||
| import Swal from "sweetalert2"; | import Swal from "sweetalert2"; | ||||
| import dayjs from "dayjs"; | import dayjs from "dayjs"; | ||||
| type DateScope = "today" | "before"; | |||||
| interface Props { | interface Props { | ||||
| open: boolean; | open: boolean; | ||||
| onClose: () => void; | onClose: () => void; | ||||
| onAssigned: () => void; | onAssigned: () => void; | ||||
| storeId: string; | storeId: string; | ||||
| truck: string; | truck: string; | ||||
| isDefaultTruck: boolean; | |||||
| defaultDateScope?: "today" | "before"; | |||||
| } | } | ||||
| const ReleasedDoPickOrderSelectModal: React.FC<Props> = ({ | const ReleasedDoPickOrderSelectModal: React.FC<Props> = ({ | ||||
| @@ -42,11 +45,12 @@ const ReleasedDoPickOrderSelectModal: React.FC<Props> = ({ | |||||
| onAssigned, | onAssigned, | ||||
| storeId, | storeId, | ||||
| truck, | truck, | ||||
| isDefaultTruck, | |||||
| }) => { | }) => { | ||||
| const { t } = useTranslation("pickOrder"); | const { t } = useTranslation("pickOrder"); | ||||
| const { data: session } = useSession() as { data: SessionWithTokens | null }; | const { data: session } = useSession() as { data: SessionWithTokens | null }; | ||||
| const currentUserId = session?.id ? parseInt(session.id) : undefined; | const currentUserId = session?.id ? parseInt(session.id) : undefined; | ||||
| const [defaultDateScope, setDefaultDateScope] = useState<"today" | "before">("today"); | |||||
| const [list, setList] = useState<ReleasedDoPickOrderListItem[]>([]); | const [list, setList] = useState<ReleasedDoPickOrderListItem[]>([]); | ||||
| const [loading, setLoading] = useState(false); | const [loading, setLoading] = useState(false); | ||||
| const [shopSearch, setShopSearch] = useState(""); | const [shopSearch, setShopSearch] = useState(""); | ||||
| @@ -56,11 +60,33 @@ const ReleasedDoPickOrderSelectModal: React.FC<Props> = ({ | |||||
| if (!open) return; | if (!open) return; | ||||
| setLoading(true); | setLoading(true); | ||||
| try { | try { | ||||
| const data = await fetchReleasedDoPickOrdersForSelection( | |||||
| shopSearch.trim() || undefined, | |||||
| storeId, | |||||
| truck?.trim() || undefined // 傳入 truck | |||||
| ); | |||||
| let data: ReleasedDoPickOrderListItem[]; // ⭐ 先宣告 | |||||
| if (isDefaultTruck) { | |||||
| if (defaultDateScope === "today") { | |||||
| // Truck X 今天 | |||||
| data = await fetchReleasedDoPickOrdersForSelectionToday( | |||||
| undefined, // shopName | |||||
| undefined, // storeId | |||||
| "車線-X" // truck | |||||
| ); | |||||
| } else { | |||||
| // Truck X 以前(/released,server 內是 < today) | |||||
| data = await fetchReleasedDoPickOrdersForSelection( | |||||
| undefined, | |||||
| undefined, | |||||
| "車線-X" | |||||
| ); | |||||
| } | |||||
| } else { | |||||
| // 一般車道 | |||||
| data = await fetchReleasedDoPickOrdersForSelection( | |||||
| shopSearch.trim() || undefined, | |||||
| storeId, | |||||
| truck?.trim() || undefined | |||||
| ); | |||||
| } | |||||
| setList(data); | setList(data); | ||||
| } catch (e) { | } catch (e) { | ||||
| console.error(e); | console.error(e); | ||||
| @@ -68,7 +94,7 @@ const ReleasedDoPickOrderSelectModal: React.FC<Props> = ({ | |||||
| } finally { | } finally { | ||||
| setLoading(false); | setLoading(false); | ||||
| } | } | ||||
| }, [open, shopSearch, storeId, truck]); | |||||
| }, [open, shopSearch, storeId, truck, isDefaultTruck, defaultDateScope]); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| loadList(); | loadList(); | ||||
| @@ -213,7 +213,7 @@ const [lastDataRefreshTime, setLastDataRefreshTime] = useState<dayjs.Dayjs | nul | |||||
| sx={{ | sx={{ | ||||
| border: '3px solid #135fed', | border: '3px solid #135fed', | ||||
| overflowX: 'auto', | overflowX: 'auto', | ||||
| maxHeight: 440, | |||||
| maxHeight: 540, | |||||
| overflow: 'auto' | overflow: 'auto' | ||||
| }} | }} | ||||
| > | > | ||||
| @@ -57,7 +57,7 @@ const QrCodeScannerProvider: React.FC<QrCodeScannerProviderProps> = ({ | |||||
| setScanResult(undefined); | setScanResult(undefined); | ||||
| resetScannerInput(); | resetScannerInput(); | ||||
| console.log("%c Scanner Reset", "color:cyan"); | |||||
| //console.log("%c Scanner Reset", "color:cyan"); | |||||
| if (error.length > 0) { | if (error.length > 0) { | ||||
| console.log("%c Error:", "color:red", error); | console.log("%c Error:", "color:red", error); | ||||
| @@ -68,25 +68,25 @@ const QrCodeScannerProvider: React.FC<QrCodeScannerProviderProps> = ({ | |||||
| const startQrCodeScanner = useCallback(() => { | const startQrCodeScanner = useCallback(() => { | ||||
| const startTime = performance.now(); | const startTime = performance.now(); | ||||
| console.log(`⏱️ [SCANNER START] Called at: ${new Date().toISOString()}`); | |||||
| //console.log(`⏱️ [SCANNER START] Called at: ${new Date().toISOString()}`); | |||||
| resetQrCodeScanner(); | resetQrCodeScanner(); | ||||
| const resetTime = performance.now() - startTime; | const resetTime = performance.now() - startTime; | ||||
| console.log(`⏱️ [SCANNER START] Reset time: ${resetTime.toFixed(2)}ms`); | |||||
| //console.log(`⏱️ [SCANNER START] Reset time: ${resetTime.toFixed(2)}ms`); | |||||
| setIsScanning(() => true); | setIsScanning(() => true); | ||||
| const setScanningTime = performance.now() - startTime; | const setScanningTime = performance.now() - startTime; | ||||
| console.log(`⏱️ [SCANNER START] setScanning time: ${setScanningTime.toFixed(2)}ms`); | |||||
| //console.log(`⏱️ [SCANNER START] setScanning time: ${setScanningTime.toFixed(2)}ms`); | |||||
| const totalTime = performance.now() - startTime; | const totalTime = performance.now() - startTime; | ||||
| console.log(`%c Scanning started `, "color:cyan"); | |||||
| console.log(`⏱️ [SCANNER START] Total start time: ${totalTime.toFixed(2)}ms`); | |||||
| console.log(`⏰ [SCANNER START] Scanner started at: ${new Date().toISOString()}`); | |||||
| //console.log(`%c Scanning started `, "color:cyan"); | |||||
| //console.log(`⏱️ [SCANNER START] Total start time: ${totalTime.toFixed(2)}ms`); | |||||
| //console.log(`⏰ [SCANNER START] Scanner started at: ${new Date().toISOString()}`); | |||||
| }, [resetQrCodeScanner]); | }, [resetQrCodeScanner]); | ||||
| const endQrCodeScanner = useCallback(() => { | const endQrCodeScanner = useCallback(() => { | ||||
| setIsScanning(() => false); | setIsScanning(() => false); | ||||
| console.log("%c Scanning stopped ", "color:cyan"); | |||||
| //console.log("%c Scanning stopped ", "color:cyan"); | |||||
| }, []); | }, []); | ||||
| // Find by rough match, return 0 if not found | // Find by rough match, return 0 if not found | ||||
| @@ -127,12 +127,12 @@ const QrCodeScannerProvider: React.FC<QrCodeScannerProviderProps> = ({ | |||||
| useEffect(() => { | useEffect(() => { | ||||
| const effectStartTime = performance.now(); | const effectStartTime = performance.now(); | ||||
| console.log(`⏱️ [KEYBOARD LISTENER EFFECT] Triggered at: ${new Date().toISOString()}`); | |||||
| console.log(`⏱️ [KEYBOARD LISTENER EFFECT] isScanning: ${isScanning}`); | |||||
| //console.log(`⏱️ [KEYBOARD LISTENER EFFECT] Triggered at: ${new Date().toISOString()}`); | |||||
| //console.log(`⏱️ [KEYBOARD LISTENER EFFECT] isScanning: ${isScanning}`); | |||||
| if (isScanning) { | if (isScanning) { | ||||
| const listenerRegisterStartTime = performance.now(); | const listenerRegisterStartTime = performance.now(); | ||||
| console.log(`⏱️ [KEYBOARD LISTENER] Registering keyboard listener at: ${new Date().toISOString()}`); | |||||
| //console.log(`⏱️ [KEYBOARD LISTENER] Registering keyboard listener at: ${new Date().toISOString()}`); | |||||
| // Reset refs when starting scan | // Reset refs when starting scan | ||||
| keysRef.current = []; | keysRef.current = []; | ||||
| @@ -147,9 +147,9 @@ const QrCodeScannerProvider: React.FC<QrCodeScannerProviderProps> = ({ | |||||
| // ✅ OPTIMIZED: Use refs to accumulate keys immediately (no state update delay) | // ✅ OPTIMIZED: Use refs to accumulate keys immediately (no state update delay) | ||||
| if (event.key.length === 1) { | if (event.key.length === 1) { | ||||
| if (isFirstKeyRef.current) { | if (isFirstKeyRef.current) { | ||||
| console.log(`⏱️ [KEYBOARD] First key press detected: "${event.key}"`); | |||||
| console.log(`⏰ [KEYBOARD] First key press at: ${keyPressTimestamp}`); | |||||
| console.log(`⏱️ [KEYBOARD] Time since listener registered: ${(keyPressTime - listenerRegisterStartTime).toFixed(2)}ms`); | |||||
| //console.log(`⏱️ [KEYBOARD] First key press detected: "${event.key}"`); | |||||
| //console.log(`⏰ [KEYBOARD] First key press at: ${keyPressTimestamp}`); | |||||
| //console.log(`⏱️ [KEYBOARD] Time since listener registered: ${(keyPressTime - listenerRegisterStartTime).toFixed(2)}ms`); | |||||
| isFirstKeyRef.current = false; | isFirstKeyRef.current = false; | ||||
| } | } | ||||
| keysRef.current.push(event.key); | keysRef.current.push(event.key); | ||||
| @@ -157,20 +157,20 @@ const QrCodeScannerProvider: React.FC<QrCodeScannerProviderProps> = ({ | |||||
| if (event.key === "{") { | if (event.key === "{") { | ||||
| const braceTime = performance.now(); | const braceTime = performance.now(); | ||||
| console.log(`⏱️ [KEYBOARD] Left brace "{" detected at: ${new Date().toISOString()}`); | |||||
| console.log(`⏱️ [KEYBOARD] Time since listener registered: ${(braceTime - listenerRegisterStartTime).toFixed(2)}ms`); | |||||
| //console.log(`⏱️ [KEYBOARD] Left brace "{" detected at: ${new Date().toISOString()}`); | |||||
| //console.log(`⏱️ [KEYBOARD] Time since listener registered: ${(braceTime - listenerRegisterStartTime).toFixed(2)}ms`); | |||||
| leftBraceCountRef.current += 1; | leftBraceCountRef.current += 1; | ||||
| } else if (event.key === "}") { | } else if (event.key === "}") { | ||||
| const braceTime = performance.now(); | const braceTime = performance.now(); | ||||
| console.log(`⏱️ [KEYBOARD] Right brace "}" detected at: ${new Date().toISOString()}`); | |||||
| console.log(`⏱️ [KEYBOARD] Time since listener registered: ${(braceTime - listenerRegisterStartTime).toFixed(2)}ms`); | |||||
| //console.log(`⏱️ [KEYBOARD] Right brace "}" detected at: ${new Date().toISOString()}`); | |||||
| //console.log(`⏱️ [KEYBOARD] Time since listener registered: ${(braceTime - listenerRegisterStartTime).toFixed(2)}ms`); | |||||
| rightBraceCountRef.current += 1; | rightBraceCountRef.current += 1; | ||||
| // ✅ OPTIMIZED: Check for complete QR immediately and update state only once | // ✅ OPTIMIZED: Check for complete QR immediately and update state only once | ||||
| if (leftBraceCountRef.current === rightBraceCountRef.current && leftBraceCountRef.current > 0) { | if (leftBraceCountRef.current === rightBraceCountRef.current && leftBraceCountRef.current > 0) { | ||||
| const completeTime = performance.now(); | const completeTime = performance.now(); | ||||
| console.log(`⏱️ [KEYBOARD] Complete QR detected immediately! Time: ${completeTime.toFixed(2)}ms`); | |||||
| console.log(`⏰ [KEYBOARD] Complete QR at: ${new Date().toISOString()}`); | |||||
| //console.log(`⏱️ [KEYBOARD] Complete QR detected immediately! Time: ${completeTime.toFixed(2)}ms`); | |||||
| //console.log(`⏰ [KEYBOARD] Complete QR at: ${new Date().toISOString()}`); | |||||
| const qrValue = keysRef.current.join("").substring( | const qrValue = keysRef.current.join("").substring( | ||||
| keysRef.current.indexOf("{"), | keysRef.current.indexOf("{"), | ||||
| @@ -223,26 +223,26 @@ const QrCodeScannerProvider: React.FC<QrCodeScannerProviderProps> = ({ | |||||
| document.addEventListener("keydown", handleKeyDown); | document.addEventListener("keydown", handleKeyDown); | ||||
| const listenerRegisterTime = performance.now() - listenerRegisterStartTime; | const listenerRegisterTime = performance.now() - listenerRegisterStartTime; | ||||
| console.log(`⏱️ [KEYBOARD LISTENER] Listener registered in: ${listenerRegisterTime.toFixed(2)}ms`); | |||||
| console.log(`⏰ [KEYBOARD LISTENER] Listener ready at: ${new Date().toISOString()}`); | |||||
| //console.log(`⏱️ [KEYBOARD LISTENER] Listener registered in: ${listenerRegisterTime.toFixed(2)}ms`); | |||||
| //console.log(`⏰ [KEYBOARD LISTENER] Listener ready at: ${new Date().toISOString()}`); | |||||
| return () => { | return () => { | ||||
| console.log(`⏱️ [KEYBOARD LISTENER] Removing keyboard listener at: ${new Date().toISOString()}`); | |||||
| // console.log(`⏱️ [KEYBOARD LISTENER] Removing keyboard listener at: ${new Date().toISOString()}`); | |||||
| document.removeEventListener("keydown", handleKeyDown); | document.removeEventListener("keydown", handleKeyDown); | ||||
| }; | }; | ||||
| } else { | } else { | ||||
| console.log(`⏱️ [KEYBOARD LISTENER EFFECT] Scanner not active, skipping listener registration`); | |||||
| //console.log(`⏱️ [KEYBOARD LISTENER EFFECT] Scanner not active, skipping listener registration`); | |||||
| } | } | ||||
| const effectTime = performance.now() - effectStartTime; | const effectTime = performance.now() - effectStartTime; | ||||
| console.log(`⏱️ [KEYBOARD LISTENER EFFECT] Total effect time: ${effectTime.toFixed(2)}ms`); | |||||
| //console.log(`⏱️ [KEYBOARD LISTENER EFFECT] Total effect time: ${effectTime.toFixed(2)}ms`); | |||||
| }, [isScanning]); | }, [isScanning]); | ||||
| // ✅ OPTIMIZED: Simplify the QR scanner effect - it's now mainly for initial detection | // ✅ OPTIMIZED: Simplify the QR scanner effect - it's now mainly for initial detection | ||||
| useEffect(() => { | useEffect(() => { | ||||
| const effectStartTime = performance.now(); | const effectStartTime = performance.now(); | ||||
| console.log(`⏱️ [QR SCANNER EFFECT] Triggered at: ${new Date().toISOString()}`); | |||||
| console.log(`⏱️ [QR SCANNER EFFECT] Keys count: ${keys.length}, leftBrace: ${leftCurlyBraceCount}, rightBrace: ${rightCurlyBraceCount}`); | |||||
| //console.log(`⏱️ [QR SCANNER EFFECT] Triggered at: ${new Date().toISOString()}`); | |||||
| //console.log(`⏱️ [QR SCANNER EFFECT] Keys count: ${keys.length}, leftBrace: ${leftCurlyBraceCount}, rightBrace: ${rightCurlyBraceCount}`); | |||||
| if (rightCurlyBraceCount > leftCurlyBraceCount || leftCurlyBraceCount > 1) { // Prevent multiple scan | if (rightCurlyBraceCount > leftCurlyBraceCount || leftCurlyBraceCount > 1) { // Prevent multiple scan | ||||
| setScanState("retry"); | setScanState("retry"); | ||||
| @@ -254,9 +254,9 @@ useEffect(() => { | |||||
| { | { | ||||
| const scanDetectedTime = performance.now(); | const scanDetectedTime = performance.now(); | ||||
| setScanState("scanning"); | setScanState("scanning"); | ||||
| console.log(`%c Scan detected, waiting for inputs...`, "color:cyan"); | |||||
| console.log(`⏱️ [QR SCANNER] Scan detected time: ${scanDetectedTime.toFixed(2)}ms`); | |||||
| console.log(`⏰ [QR SCANNER] Scan detected at: ${new Date().toISOString()}`); | |||||
| // console.log(`%c Scan detected, waiting for inputs...`, "color:cyan"); | |||||
| //console.log(`⏱️ [QR SCANNER] Scan detected time: ${scanDetectedTime.toFixed(2)}ms`); | |||||
| //console.log(`⏰ [QR SCANNER] Scan detected at: ${new Date().toISOString()}`); | |||||
| } | } | ||||
| // Note: Complete QR detection is now handled directly in handleKeyDown | // Note: Complete QR detection is now handled directly in handleKeyDown | ||||
| // This effect is mainly for UI feedback and error handling | // This effect is mainly for UI feedback and error handling | ||||
| @@ -266,13 +266,13 @@ useEffect(() => { | |||||
| useEffect(() => { | useEffect(() => { | ||||
| if (qrCodeScannerValues.length > 0) { | if (qrCodeScannerValues.length > 0) { | ||||
| const processStartTime = performance.now(); | const processStartTime = performance.now(); | ||||
| console.log(`⏱️ [QR SCANNER PROCESS] Processing qrCodeScannerValues at: ${new Date().toISOString()}`); | |||||
| console.log(`⏱️ [QR SCANNER PROCESS] Values count: ${qrCodeScannerValues.length}`); | |||||
| // console.log(`⏱️ [QR SCANNER PROCESS] Processing qrCodeScannerValues at: ${new Date().toISOString()}`); | |||||
| //console.log(`⏱️ [QR SCANNER PROCESS] Values count: ${qrCodeScannerValues.length}`); | |||||
| const scannedValues = qrCodeScannerValues[0]; | const scannedValues = qrCodeScannerValues[0]; | ||||
| console.log(`%c Scanned Result: `, "color:cyan", scannedValues); | |||||
| console.log(`⏱️ [QR SCANNER PROCESS] Scanned value: ${scannedValues}`); | |||||
| console.log(`⏰ [QR SCANNER PROCESS] Processing at: ${new Date().toISOString()}`); | |||||
| //console.log(`%c Scanned Result: `, "color:cyan", scannedValues); | |||||
| //console.log(`⏱️ [QR SCANNER PROCESS] Scanned value: ${scannedValues}`); | |||||
| //console.log(`⏰ [QR SCANNER PROCESS] Processing at: ${new Date().toISOString()}`); | |||||
| if (scannedValues.substring(0, 8) == "{2fitest") { // DEBUGGING | if (scannedValues.substring(0, 8) == "{2fitest") { // DEBUGGING | ||||
| // 先检查是否是 {2fiteste...} 或 {2fitestu...} 格式 | // 先检查是否是 {2fiteste...} 或 {2fitestu...} 格式 | ||||
| @@ -310,7 +310,7 @@ useEffect(() => { | |||||
| } | } | ||||
| setScanResult(debugValue); | setScanResult(debugValue); | ||||
| const processTime = performance.now() - processStartTime; | const processTime = performance.now() - processStartTime; | ||||
| console.log(`⏱️ [QR SCANNER PROCESS] Non-numeric processing time: ${processTime.toFixed(2)}ms`); | |||||
| // console.log(`⏱️ [QR SCANNER PROCESS] Non-numeric processing time: ${processTime.toFixed(2)}ms`); | |||||
| return; | return; | ||||
| } | } | ||||
| } | } | ||||
| @@ -319,8 +319,8 @@ useEffect(() => { | |||||
| const parseStartTime = performance.now(); | const parseStartTime = performance.now(); | ||||
| const data: QrCodeInfo = JSON.parse(scannedValues); | const data: QrCodeInfo = JSON.parse(scannedValues); | ||||
| const parseTime = performance.now() - parseStartTime; | const parseTime = performance.now() - parseStartTime; | ||||
| console.log(`%c Parsed scan data`, "color:green", data); | |||||
| console.log(`⏱️ [QR SCANNER PROCESS] JSON parse time: ${parseTime.toFixed(2)}ms`); | |||||
| // console.log(`%c Parsed scan data`, "color:green", data); | |||||
| //console.log(`⏱️ [QR SCANNER PROCESS] JSON parse time: ${parseTime.toFixed(2)}ms`); | |||||
| const content = scannedValues.substring(1, scannedValues.length - 1); | const content = scannedValues.substring(1, scannedValues.length - 1); | ||||
| data.value = content; | data.value = content; | ||||
| @@ -328,14 +328,14 @@ useEffect(() => { | |||||
| const setResultStartTime = performance.now(); | const setResultStartTime = performance.now(); | ||||
| setScanResult(data); | setScanResult(data); | ||||
| const setResultTime = performance.now() - setResultStartTime; | const setResultTime = performance.now() - setResultStartTime; | ||||
| console.log(`⏱️ [QR SCANNER PROCESS] setScanResult time: ${setResultTime.toFixed(2)}ms`); | |||||
| console.log(`⏰ [QR SCANNER PROCESS] setScanResult at: ${new Date().toISOString()}`); | |||||
| // console.log(`⏱️ [QR SCANNER PROCESS] setScanResult time: ${setResultTime.toFixed(2)}ms`); | |||||
| //console.log(`⏰ [QR SCANNER PROCESS] setScanResult at: ${new Date().toISOString()}`); | |||||
| const processTime = performance.now() - processStartTime; | const processTime = performance.now() - processStartTime; | ||||
| console.log(`⏱️ [QR SCANNER PROCESS] Total processing time: ${processTime.toFixed(2)}ms`); | |||||
| // console.log(`⏱️ [QR SCANNER PROCESS] Total processing time: ${processTime.toFixed(2)}ms`); | |||||
| } catch (error) { // Rough match for other scanner input -- Pending Review | } catch (error) { // Rough match for other scanner input -- Pending Review | ||||
| console.log(`⏱️ [QR SCANNER PROCESS] JSON parse failed, trying rough match`); | |||||
| //console.log(`⏱️ [QR SCANNER PROCESS] JSON parse failed, trying rough match`); | |||||
| const silId = findIdByRoughMatch(scannedValues, "StockInLine").number ?? 0; | const silId = findIdByRoughMatch(scannedValues, "StockInLine").number ?? 0; | ||||
| if (silId == 0) { | if (silId == 0) { | ||||
| @@ -39,6 +39,8 @@ | |||||
| "Lines Per Pick Order": "每提料單行數", | "Lines Per Pick Order": "每提料單行數", | ||||
| "Pick Orders Details": "提料單詳情", | "Pick Orders Details": "提料單詳情", | ||||
| "Lines": "行數", | "Lines": "行數", | ||||
| "Before Today": "以前", | |||||
| "Truck X": "車線-X", | |||||
| "Finsihed good items": "成品項目", | "Finsihed good items": "成品項目", | ||||
| "kinds": "款", | "kinds": "款", | ||||
| "Completed Date": "完成日期", | "Completed Date": "完成日期", | ||||