| @@ -74,6 +74,8 @@ const DoWorkbenchTabsInner: React.FC<Props> = ({ defaultTabIndex = 0, printerCom | |||
| urlTargetDateRaw && urlTargetDateRaw.trim() !== "" | |||
| ? decodeURIComponent(urlTargetDateRaw.trim()) | |||
| : null; | |||
| /** Item Tracing deep-link only; DO finish redirect must not set this. */ | |||
| const urlOpenDetail = searchParams.get("openDetail") === "1"; | |||
| const [tab, setTab] = React.useState<number>(defaultTabIndex); | |||
| const [lanePanelPrefs, setLanePanelPrefs] = React.useState<WorkbenchLanePanelPrefs>( | |||
| @@ -147,10 +149,11 @@ const DoWorkbenchTabsInner: React.FC<Props> = ({ defaultTabIndex = 0, printerCom | |||
| setTab(newTab); | |||
| const params = new URLSearchParams(searchParams.toString()); | |||
| params.set("tab", String(newTab)); | |||
| /* ticketNo / targetDate deep-link for Finished Good Record tabs */ | |||
| /* ticketNo / targetDate / openDetail deep-link for Finished Good Record tabs */ | |||
| if (newTab !== 2 && newTab !== 3) { | |||
| params.delete("ticketNo"); | |||
| params.delete("targetDate"); | |||
| params.delete("openDetail"); | |||
| } | |||
| const qs = params.toString(); | |||
| router.replace(qs ? `${pathname}?${qs}` : pathname, { scroll: false }); | |||
| @@ -380,24 +383,26 @@ const DoWorkbenchTabsInner: React.FC<Props> = ({ defaultTabIndex = 0, printerCom | |||
| </TabPanel> | |||
| <TabPanel value={tab} index={2}> | |||
| <GoodPickExecutionWorkbenchRecord | |||
| key={`workbench-record-mine-${urlTicketNo ?? ""}-${urlTargetDate ?? ""}`} | |||
| key={`workbench-record-mine-${urlTicketNo ?? ""}-${urlTargetDate ?? ""}-${urlOpenDetail ? "1" : "0"}`} | |||
| printerCombo={printerCombo} | |||
| listScope="mine" | |||
| a4Printer={a4Printer} | |||
| labelPrinter={labelPrinter} | |||
| initialTicketNo={urlTicketNo} | |||
| initialTargetDate={urlTargetDate} | |||
| openDetail={urlOpenDetail} | |||
| /> | |||
| </TabPanel> | |||
| <TabPanel value={tab} index={3}> | |||
| <GoodPickExecutionWorkbenchRecord | |||
| key={`workbench-record-all-${urlTicketNo ?? ""}-${urlTargetDate ?? ""}`} | |||
| key={`workbench-record-all-${urlTicketNo ?? ""}-${urlTargetDate ?? ""}-${urlOpenDetail ? "1" : "0"}`} | |||
| printerCombo={printerCombo} | |||
| listScope="all" | |||
| a4Printer={a4Printer} | |||
| labelPrinter={labelPrinter} | |||
| initialTicketNo={urlTicketNo} | |||
| initialTargetDate={urlTargetDate} | |||
| openDetail={urlOpenDetail} | |||
| /> | |||
| </TabPanel> | |||
| <TabPanel value={tab} index={4}> | |||
| @@ -48,6 +48,8 @@ type Props = { | |||
| labelPrinter: PrinterCombo | null; | |||
| initialTicketNo?: string | null; | |||
| initialTargetDate?: string | null; | |||
| /** When true (Item Tracing `openDetail=1`), auto-open matching ticket detail. */ | |||
| openDetail?: boolean; | |||
| }; | |||
| const GoodPickExecutionWorkbenchRecord: React.FC<Props> = ({ | |||
| @@ -57,6 +59,7 @@ const GoodPickExecutionWorkbenchRecord: React.FC<Props> = ({ | |||
| labelPrinter, | |||
| initialTicketNo, | |||
| initialTargetDate, | |||
| openDetail = false, | |||
| }) => { | |||
| const { t } = useTranslation("pickOrder"); | |||
| const { data: session } = useSession() as { data: SessionWithTokens | null }; | |||
| @@ -509,13 +512,14 @@ const GoodPickExecutionWorkbenchRecord: React.FC<Props> = ({ | |||
| const initialDetailOpenedRef = useRef(false); | |||
| useEffect(() => { | |||
| if (!openDetail) return; | |||
| if (initialDetailOpenedRef.current || loading || !initialTicketNo?.trim()) return; | |||
| const tn = initialTicketNo.trim(); | |||
| const match = records.find((r) => r.ticketNo?.trim() === tn); | |||
| if (!match) return; | |||
| initialDetailOpenedRef.current = true; | |||
| void handleDetailClick(match); | |||
| }, [records, loading, initialTicketNo, handleDetailClick]); | |||
| }, [openDetail, records, loading, initialTicketNo, handleDetailClick]); | |||
| const handleBackToList = useCallback(() => { | |||
| setShowDetailView(false); | |||
| @@ -47,6 +47,8 @@ const ItemTracingDocLink: React.FC<DocLinkProps> = ({ | |||
| params.set("tab", String(workbenchTab)); | |||
| params.set("ticketNo", ticketNo.trim()); | |||
| if (linkTargetDate) params.set("targetDate", linkTargetDate); | |||
| // Only Item Tracing deep-links should auto-open detail (not DO finish redirect). | |||
| params.set("openDetail", "1"); | |||
| href = `/doworkbench?${params.toString()}`; | |||
| } else if (kind === "jodetail" && (consoCode || code)) { | |||
| const params = new URLSearchParams(); | |||
| @@ -54,6 +56,7 @@ const ItemTracingDocLink: React.FC<DocLinkProps> = ({ | |||
| // Prefer PI-* pick order code for jodetail search; consoCode may be PICK-*. | |||
| params.set("pickOrderCode", (code || consoCode || "").trim()); | |||
| if (linkTargetDate) params.set("targetDate", linkTargetDate); | |||
| params.set("openDetail", "1"); | |||
| href = `/jodetail?${params.toString()}`; | |||
| } else if (kind === "pick") { | |||
| // /pickOrder/detail?consoCode=… is retired — only link when we have a TI-* workbench ticket. | |||
| @@ -65,6 +68,7 @@ const ItemTracingDocLink: React.FC<DocLinkProps> = ({ | |||
| params.set("tab", String(workbenchTab)); | |||
| params.set("ticketNo", workbenchTicket); | |||
| if (linkTargetDate) params.set("targetDate", linkTargetDate); | |||
| params.set("openDetail", "1"); | |||
| href = `/doworkbench?${params.toString()}`; | |||
| } else { | |||
| return <>{code}</>; | |||
| @@ -62,6 +62,8 @@ const JodetailSearch: React.FC<Props> = ({ printerCombo }) => { | |||
| const match = urlTargetDateRaw.match(/(\d{4}-\d{2}-\d{2})/); | |||
| return match ? match[1] : undefined; | |||
| })(); | |||
| /** Item Tracing deep-link only; other navigations must not set this. */ | |||
| const urlOpenDetail = searchParams.get("openDetail") === "1"; | |||
| const urlTabIndex = useMemo(() => { | |||
| if (urlTabRaw == null) return undefined; | |||
| const n = parseInt(urlTabRaw, 10); | |||
| @@ -513,6 +515,7 @@ const JodetailSearch: React.FC<Props> = ({ printerCombo }) => { | |||
| printQty={printQty} | |||
| initialPickOrderCode={urlPickOrderCode} | |||
| initialTargetDate={urlTargetDate} | |||
| openDetail={urlOpenDetail} | |||
| /> | |||
| )} | |||
| {tabIndex === 2 && <MaterialPickStatusTable />} | |||
| @@ -60,6 +60,8 @@ interface Props { | |||
| printQty?: number; | |||
| initialPickOrderCode?: string; | |||
| initialTargetDate?: string; | |||
| /** When true (Item Tracing `openDetail=1`), auto-open matching pick-order detail. */ | |||
| openDetail?: boolean; | |||
| } | |||
| // 修改:已完成的 Job Order Pick Order 接口 | |||
| @@ -121,6 +123,7 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ | |||
| printQty: printQtyProp, | |||
| initialPickOrderCode, | |||
| initialTargetDate, | |||
| openDetail = false, | |||
| }) => { | |||
| const { t } = useTranslation("jo"); | |||
| const router = useRouter(); | |||
| @@ -383,6 +386,7 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ | |||
| }, [fetchLotDetailsData]); | |||
| useEffect(() => { | |||
| if (!openDetail) return; | |||
| if (initialDetailOpenedRef.current || completedJobOrderPickOrdersLoading) return; | |||
| const code = initialPickOrderCode?.trim(); | |||
| if (!code) return; | |||
| @@ -395,6 +399,7 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ | |||
| initialDetailOpenedRef.current = true; | |||
| void handleDetailClick(match); | |||
| }, [ | |||
| openDetail, | |||
| completedJobOrderPickOrders, | |||
| completedJobOrderPickOrdersLoading, | |||
| initialPickOrderCode, | |||