From 5dc3f8b062349b869bd2244cbdce06a887a9597b Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Wed, 12 Aug 2026 21:39:03 +0800 Subject: [PATCH] =?UTF-8?q?history.replaceState=20=E5=8F=AA=E6=94=B9=20que?= =?UTF-8?q?ry=E3=80=81=E4=B8=8D=E8=B5=B0=20soft=20nav=20selectedPolIdRef?= =?UTF-8?q?=20=E5=9C=A8=20searchParams=20=E4=B8=8D=E6=9C=83=E5=8D=B3?= =?UTF-8?q?=E6=99=82=E6=9B=B4=E6=96=B0=E6=99=82=E4=BB=8D=E7=B6=AD=E6=8C=81?= =?UTF-8?q?=E9=81=B8=E5=8F=96=20PoInputGrid=20=E7=94=A8=20getLiveStockInLi?= =?UTF-8?q?neId()=20=E8=AE=80=20live=20URL=EF=BC=8C=E4=B8=A6=E6=B8=85?= =?UTF-8?q?=E6=8E=89=E5=88=A5=E6=A2=9D=20POL=20=E7=9A=84=20stale=20stockIn?= =?UTF-8?q?LineId?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/PoDetail/PoDetail.tsx | 93 +++++++++++------- src/components/PoDetail/PoInputGrid.tsx | 107 +++++++++++++++------ src/components/PoDetail/QcStockInModal.tsx | 2 +- src/components/Qc/QcStockInModal.tsx | 2 +- 4 files changed, 137 insertions(+), 67 deletions(-) diff --git a/src/components/PoDetail/PoDetail.tsx b/src/components/PoDetail/PoDetail.tsx index 40bfbe2f..1b1fb889 100644 --- a/src/components/PoDetail/PoDetail.tsx +++ b/src/components/PoDetail/PoDetail.tsx @@ -60,6 +60,7 @@ import { useContext, useEffect, useMemo, + useRef, useState, } from "react"; import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; @@ -252,7 +253,7 @@ interface PolInputResult { dnQty: string, } -/** FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.0 | 2026-08-06 */ +/** FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.1 | 2026-08-12 */ const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { const cameras = useContext(CameraContext); const { data: session } = useSession(); @@ -302,16 +303,43 @@ const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { const [selectedRow, setSelectedRow] = useState(null); const [stockInLine, setStockInLine] = useState([]); const [processedQty, setProcessedQty] = useState(0); + /** Tracks user/nav selection so query patches via history.replaceState stay authoritative. */ + const selectedPolIdRef = useRef(null); + + /** Patch PO edit query without Next soft-navigation (avoids scroll-to-top). */ + const patchPoEditQuery = useCallback( + (mutate: (params: URLSearchParams) => void) => { + if (typeof window === "undefined") return; + const params = new URLSearchParams(window.location.search); + mutate(params); + const qs = params.toString(); + window.history.replaceState( + window.history.state, + "", + qs ? `${pathname}?${qs}` : pathname, + ); + }, + [pathname], + ); + /** Keep selection + bottom stock-in grid in sync with selected / URL `polId`. */ useEffect(() => { - const polIdParam = searchParams.get("polId"); - if (!polIdParam || rows.length === 0) return; - const match = rows.find((r) => r.id.toString() === polIdParam); - if (match) { - setSelectedRow(match); - setStockInLine(match.stockInLine); - setProcessedQty(match.processed); - } + if (rows.length === 0) return; + const urlPolId = searchParams.get("polId"); + const preferredId = + selectedPolIdRef.current ?? + (urlPolId != null ? Number(urlPolId) : null); + if (preferredId == null || Number.isNaN(preferredId)) return; + const match = + rows.find((r) => r.id === preferredId) ?? + (urlPolId != null + ? rows.find((r) => r.id.toString() === urlPolId) + : undefined); + if (!match) return; + selectedPolIdRef.current = match.id; + setSelectedRow(match); + setStockInLine(match.stockInLine ?? []); + setProcessedQty(match.processed); }, [rows, searchParams]); const router = useRouter(); @@ -465,9 +493,10 @@ const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { }); setRows(result.pol || []); if (result.pol && result.pol.length > 0) { - const targetPolId = preferredPolId ?? selectedRow?.id; + const targetPolId = preferredPolId ?? selectedPolIdRef.current ?? selectedRow?.id; const targetPol = result.pol.find((p) => p.id === targetPolId) ?? result.pol[0]; + selectedPolIdRef.current = targetPol.id; setSelectedRow(targetPol); setStockInLine(targetPol.stockInLine); setProcessedQty(targetPol.processed); @@ -482,6 +511,7 @@ const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { const handlePoSelect = useCallback( async (selectedPo: PoResult) => { if (selectedPo.id === selectedPoId) return; + selectedPolIdRef.current = null; setSelectedPoId(selectedPo.id); await fetchPoDetail(selectedPo.id.toString()); const newSelectedIds = selectedIdsParam || selectedPo.id.toString(); @@ -570,13 +600,6 @@ const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { () => returnWeightUnit(row.uom), [row.uom], ); - useEffect(() => { - const polId = searchParams.get("polId") != null ? parseInt(searchParams.get("polId")!) : null - if (polId) { - setStockInLine(rows.find((r) => r.id == polId)!.stockInLine) - } - }, []); - useEffect(() => { // `processedQty` comes from putAwayLines (stock unit). // After the fix, `row.qty` is qtyM18 (M18 unit), so compare using stockUom demand. @@ -595,23 +618,22 @@ const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { setDnQtyInput(polInputList[row.id]?.dnQty ?? ""); }, [polInputList, row.id]); - const handleRowSelect = () => { - // setSelectedRowId(row.id); - setSelectedRow(row); - setStockInLine(row.stockInLine); - setProcessedQty(row.processed); - }; const changeStockInLines = useCallback( (id: number) => { - //rows = purchaseOrderLine - const target = rows.find((r) => r.id === id) - const stockInLine = target!.stockInLine - setStockInLine(stockInLine) - setSelectedRow(target!) - // console.log(pathname) - // router.replace(`/po/edit?id=${item.poId}&polId=${item.polId}&stockInLineId=${item.stockInLineId}`); + const target = rows.find((r) => r.id === id); + if (!target) return; + selectedPolIdRef.current = id; + setSelectedRow(target); + setStockInLine(target.stockInLine ?? []); + setProcessedQty(target.processed); + + // history.replaceState: keep URL in sync without scrolling to top + patchPoEditQuery((params) => { + params.set("polId", String(id)); + params.delete("stockInLineId"); + }); }, - [rows] + [rows, patchPoEditQuery], ); const handleStart = useCallback( @@ -644,7 +666,12 @@ const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { ...prev, [row.id]: { lotNo: "", dnQty: "" }, })); + selectedPolIdRef.current = row.id; setSelectedRow(row); + patchPoEditQuery((params) => { + params.set("polId", String(row.id)); + params.delete("stockInLineId"); + }); fetchPoDetail(selectedPoId.toString(), true, row.id); } console.log(res); @@ -662,7 +689,7 @@ const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { doSubmit(); } }, - [dnQtyInput, row, dnFormProps, selectedPoId, fetchPoDetail, t, lotNoInput], + [dnQtyInput, row, dnFormProps, selectedPoId, fetchPoDetail, t, lotNoInput, patchPoEditQuery], ); const syncRowInputToParent = useCallback((lotNo: string, dnQty: string) => { @@ -746,8 +773,6 @@ const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { )} e.stopPropagation()} /> diff --git a/src/components/PoDetail/PoInputGrid.tsx b/src/components/PoDetail/PoInputGrid.tsx index e2771b79..bd67ae59 100644 --- a/src/components/PoDetail/PoInputGrid.tsx +++ b/src/components/PoDetail/PoInputGrid.tsx @@ -36,7 +36,7 @@ import PlayArrowIcon from "@mui/icons-material/PlayArrow"; import { PurchaseOrderLine } from "@/app/api/po"; import { StockInLine } from "@/app/api/stockIn"; import { createStockInLine, deleteStockInLine, QcResult } from "@/app/api/stockIn/actions"; -import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { usePathname, useSearchParams } from "next/navigation"; import { returnWeightUnit, calculateWeight, @@ -170,6 +170,7 @@ class ProcessRowUpdateError extends Error { } } +/** FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.1 | 2026-08-12 */ function PoInputGrid({ // qc, setRows, @@ -204,7 +205,6 @@ function PoInputGrid({ StockInLine & { qcResult?: QcResult[] } & { escalationResult?: EscalationResult[] } >(); const pathname = usePathname() - const router = useRouter(); const searchParams = useSearchParams(); const [qcOpen, setQcOpen] = useState(false); @@ -384,15 +384,35 @@ function PoInputGrid({ // ); const [newOpen, setNewOpen] = useState(false); - const stockInLineId = searchParams.get("stockInLineId"); + const stockInLineIdFromNext = searchParams.get("stockInLineId"); const poLineId = searchParams.get("poLineId"); - const closeNewModal = useCallback((updatedStockInLine?: StockInLine) => { - const newParams = new URLSearchParams(searchParams.toString()); - newParams.delete("stockInLineId"); + const patchQuery = useCallback( + (mutate: (params: URLSearchParams) => void) => { + if (typeof window === "undefined") return; + const params = new URLSearchParams(window.location.search); + mutate(params); + const qs = params.toString(); + window.history.replaceState( + window.history.state, + "", + qs ? `${pathname}?${qs}` : pathname, + ); + }, + [pathname], + ); + + const getLiveStockInLineId = useCallback((): string | null => { if (typeof window !== "undefined") { - window.history.replaceState({}, "", `${pathname}?${newParams.toString()}`); + return new URLSearchParams(window.location.search).get("stockInLineId"); } + return stockInLineIdFromNext; + }, [stockInLineIdFromNext]); + + const closeNewModal = useCallback((updatedStockInLine?: StockInLine) => { + patchQuery((params) => { + params.delete("stockInLineId"); + }); setNewOpen(false); if (updatedStockInLine?.id != null) { @@ -403,7 +423,7 @@ function PoInputGrid({ (prev || []).map((p) => (p.id === updatedStockInLine.id ? { ...p, ...updatedStockInLine } : p)) ); } - }, [pathname, searchParams]); + }, [patchQuery, setStockInLine]); // Open modal const openNewModal = useCallback(() => { @@ -413,42 +433,67 @@ function PoInputGrid({ // Button handler to update the URL and open the modal const handleNewQC = useCallback( (id: GridRowId, params: any) => async() => { - // setBtnIsLoading(true); + if (!params?.row) return; setRowModesModel((prev) => ({ ...prev, [id]: { mode: GridRowModes.View }, })); - - // const qcResult = await fetchQcDefaultValue(id); - // const escResult = await fetchEscalationLogsByStockInLines([Number(id)]); + setModalInfo(() => ({ ...params.row, - // qcResult: qcResult, - // escResult: escResult, receivedQty: itemDetail.receivedQty, })); - - const newParams = new URLSearchParams(searchParams.toString()); - newParams.set("stockInLineId", id.toString()); // Ensure `set` to avoid duplicates - router.replace(`${pathname}?${newParams.toString()}`); - openNewModal() - // setTimeout(() => { - // }, 200); + + // Avoid router.replace — it scrolls the page to top + patchQuery((params) => { + params.set("stockInLineId", id.toString()); + }); + openNewModal(); }, - [openNewModal, pathname, router, searchParams] + [openNewModal, patchQuery, itemDetail.receivedQty], ); - // Open modal if `stockInLineId` exists in the URL - const [firstCheckForSil, setFirstCheckForSil] = useState(false) + // Open modal if `stockInLineId` exists in the live URL (and belongs to current grid) + const [firstCheckForSil, setFirstCheckForSil] = useState(false); useEffect(() => { - if (stockInLineId && itemDetail && !firstCheckForSil) { - // console.log(stockInLineId) - // console.log(apiRef.current.getRow(stockInLineId)) - setFirstCheckForSil(true) - const fn = handleNewQC(stockInLineId, {row: apiRef.current.getRow(stockInLineId)}); - fn(); + setFirstCheckForSil(false); + }, [itemDetail.id]); + useEffect(() => { + if (!itemDetail || firstCheckForSil) return; + + const liveStockInLineId = getLiveStockInLineId(); + if (!liveStockInLineId) { + setFirstCheckForSil(true); + return; + } + + const row = apiRef.current.getRow(Number(liveStockInLineId)); + if (!row) { + // Stale query from another POL: drop it once current entries are known + if ( + entries.length > 0 && + !entries.some((e) => String(e.id) === String(liveStockInLineId)) + ) { + patchQuery((params) => { + params.delete("stockInLineId"); + }); + setFirstCheckForSil(true); + } + return; } - }, [stockInLineId, poLineId, itemDetail]); + + setFirstCheckForSil(true); + void handleNewQC(liveStockInLineId, { row })(); + }, [ + stockInLineIdFromNext, + poLineId, + itemDetail, + firstCheckForSil, + entries, + handleNewQC, + getLiveStockInLineId, + patchQuery, + ]); const handleEscalation = useCallback( (id: GridRowId, params: any) => () => { // setBtnIsLoading(true); diff --git a/src/components/PoDetail/QcStockInModal.tsx b/src/components/PoDetail/QcStockInModal.tsx index 45b361ea..d817a603 100644 --- a/src/components/PoDetail/QcStockInModal.tsx +++ b/src/components/PoDetail/QcStockInModal.tsx @@ -71,7 +71,7 @@ interface CommonProps extends Omit { interface Props extends CommonProps { // itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] }; } -/** FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.0 | 2026-08-06 */ +/** FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.1 | 2026-08-12 */ const PoQcStockInModalVer2: React.FC = ({ open, onClose, diff --git a/src/components/Qc/QcStockInModal.tsx b/src/components/Qc/QcStockInModal.tsx index 9f093c2e..8607d15c 100644 --- a/src/components/Qc/QcStockInModal.tsx +++ b/src/components/Qc/QcStockInModal.tsx @@ -72,7 +72,7 @@ interface Props extends CommonProps { // itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] }; } -/** FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.0 | 2026-08-06 */ +/** FP-MTMS Version Checklist | Functions Ref. No. 56 | v1.0.1 | 2026-08-12 */ const QcStockInModal: React.FC = ({ open, onClose,