Просмотр исходного кода

history.replaceState 只改 query、不走 soft nav

selectedPolIdRef 在 searchParams 不會即時更新時仍維持選取
PoInputGrid 用 getLiveStockInLineId() 讀 live URL,並清掉別條 POL 的 stale stockInLineId
fix負數倉
CANCERYS\kw093 1 месяц назад
Родитель
Сommit
5dc3f8b062
4 измененных файлов: 137 добавлений и 67 удалений
  1. +59
    -34
      src/components/PoDetail/PoDetail.tsx
  2. +76
    -31
      src/components/PoDetail/PoInputGrid.tsx
  3. +1
    -1
      src/components/PoDetail/QcStockInModal.tsx
  4. +1
    -1
      src/components/Qc/QcStockInModal.tsx

+ 59
- 34
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<Props> = ({ po, warehouse, printerCombo }) => {
const cameras = useContext(CameraContext);
const { data: session } = useSession();
@@ -302,16 +303,43 @@ const PoDetail: React.FC<Props> = ({ po, warehouse, printerCombo }) => {
const [selectedRow, setSelectedRow] = useState<PurchaseOrderLine | null>(null);
const [stockInLine, setStockInLine] = useState<StockInLine[]>([]);
const [processedQty, setProcessedQty] = useState(0);
/** Tracks user/nav selection so query patches via history.replaceState stay authoritative. */
const selectedPolIdRef = useRef<number | null>(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<Props> = ({ 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<Props> = ({ 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<Props> = ({ 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<Props> = ({ 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<Props> = ({ 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<Props> = ({ 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<Props> = ({ po, warehouse, printerCombo }) => {
)}
<Radio
checked={selectedRow?.id === row.id}
// onChange={handleRowSelect}
// onClick={(e) => e.stopPropagation()}
/>
</TableCell>
<TableCell align="left" sx={{ width: 88, maxWidth: 88, px: 1, whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }} title={row.itemNo}>


+ 76
- 31
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);


+ 1
- 1
src/components/PoDetail/QcStockInModal.tsx Просмотреть файл

@@ -71,7 +71,7 @@ interface CommonProps extends Omit<ModalProps, "children"> {
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<Props> = ({
open,
onClose,


+ 1
- 1
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<Props> = ({
open,
onClose,


Загрузка…
Отмена
Сохранить