From 673b6818bff5af6de5db5783889975f13c0d0fbf Mon Sep 17 00:00:00 2001 From: "B.E.N.S.O.N" Date: Mon, 20 Apr 2026 16:32:42 +0800 Subject: [PATCH] PO Update --- src/components/PoDetail/PoDetail.tsx | 130 +++++++++++---------------- 1 file changed, 54 insertions(+), 76 deletions(-) diff --git a/src/components/PoDetail/PoDetail.tsx b/src/components/PoDetail/PoDetail.tsx index b83fdd1..98ccb20 100644 --- a/src/components/PoDetail/PoDetail.tsx +++ b/src/components/PoDetail/PoDetail.tsx @@ -76,7 +76,6 @@ import { Controller, FormProvider, useForm } from "react-hook-form"; import dayjs, { Dayjs } from "dayjs"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import { DatePicker, LocalizationProvider, zhHK } from "@mui/x-date-pickers"; -import { debounce } from "lodash"; import LoadingComponent from "../General/LoadingComponent"; import { getMailTemplatePdfForStockInLine } from "@/app/api/mailTemplate/actions"; import { PrinterCombo } from "@/app/api/settings/printer"; @@ -233,7 +232,7 @@ const PoSearchList: React.FC<{ interface PolInputResult { lotNo: string, - dnQty: number, + dnQty: string, } const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { @@ -250,15 +249,19 @@ const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { const [rows, setRows] = useState( purchaseOrder.pol || [], ); - const [polInputList, setPolInputList] = useState([]) + const [polInputList, setPolInputList] = useState>({}) const PO_DETAIL_SELECTION_KEY = "po-detail-selection"; useEffect(() => { - setPolInputList( - (purchaseOrder.pol ?? []).map(() => ({ - lotNo: "", - dnQty: 0, - } as PolInputResult)) - ); + setPolInputList((prev) => { + const next: Record = {}; + (purchaseOrder.pol ?? []).forEach((pol) => { + next[pol.id] = prev[pol.id] ?? { + lotNo: "", + dnQty: "", + }; + }); + return next; + }); }, [purchaseOrder.pol]); useEffect(() => { try { @@ -335,7 +338,7 @@ const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { }, [selectedIdsParam]); - const fetchPoDetail = useCallback(async (poId: string, preserveDnNo: boolean = false) => { + const fetchPoDetail = useCallback(async (poId: string, preserveDnNo: boolean = false, preferredPolId?: number) => { try { const result = await fetchPoInClient(parseInt(poId)); if (result) { @@ -348,16 +351,12 @@ const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { }); setRows(result.pol || []); if (result.pol && result.pol.length > 0) { - if (result.id === selectedPoId && selectedRow?.id) { - const polIndex = result.pol.findIndex((p) => p.id === selectedRow?.id) - // setSelectedRow(result.pol[polIndex]); - setStockInLine(result.pol[polIndex].stockInLine); - setProcessedQty(result.pol[polIndex].processed); - } else { - // setSelectedRow(result.pol[0]); - setStockInLine(result.pol[0].stockInLine); - setProcessedQty(result.pol[0].processed); - } + const targetPolId = preferredPolId ?? selectedRow?.id; + const targetPol = + result.pol.find((p) => p.id === targetPolId) ?? result.pol[0]; + setSelectedRow(targetPol); + setStockInLine(targetPol.stockInLine); + setProcessedQty(targetPol.processed); } // if (focusField) {console.log(focusField);focusField.focus();} } @@ -446,6 +445,8 @@ const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { // const [open, setOpen] = useState(false); const [processedQty, setProcessedQty] = useState(row.processed); const [currStatus, setCurrStatus] = useState(row.status); + const [lotNoInput, setLotNoInput] = useState(polInputList[row.id]?.lotNo ?? ""); + const [dnQtyInput, setDnQtyInput] = useState(polInputList[row.id]?.dnQty ?? ""); // const [stockInLine, setStockInLine] = useState(row.stockInLine); const totalWeight = useMemo( () => calculateWeight(row.qty, row.uom), @@ -455,10 +456,6 @@ const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { () => returnWeightUnit(row.uom), [row.uom], ); - const rowIndex = useMemo(() => { - return rows.findIndex((r) => r.id === row.id) - }, []) - useEffect(() => { const polId = searchParams.get("polId") != null ? parseInt(searchParams.get("polId")!) : null if (polId) { @@ -479,6 +476,11 @@ const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { } }, [processedQty, row.qty, row.stockUom?.stockQty]); + useEffect(() => { + setLotNoInput(polInputList[row.id]?.lotNo ?? ""); + setDnQtyInput(polInputList[row.id]?.dnQty ?? ""); + }, [polInputList, row.id]); + const handleRowSelect = () => { // setSelectedRowId(row.id); setSelectedRow(row); @@ -501,7 +503,7 @@ const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { const handleStart = useCallback( () => { const orderQty = Number(row?.qty) ?? 0; - const acceptedQty = Number(polInputList[rowIndex].dnQty); + const acceptedQty = Number(dnQtyInput.trim()); if (isNaN(acceptedQty) || acceptedQty <= 0) { alert("來貨數量必須大於0!"); @@ -518,11 +520,18 @@ const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { itemName: row.itemName, purchaseOrderLineId: row.id, acceptedQty: acceptedQty, - productLotNo: polInputList[rowIndex].lotNo || '', + productLotNo: lotNoInput || "", }; const res = await createStockInLine(postData); if (res) { - fetchPoDetail(selectedPoId.toString(), true); + setLotNoInput(""); + setDnQtyInput(""); + setPolInputList((prev) => ({ + ...prev, + [row.id]: { lotNo: "", dnQty: "" }, + })); + setSelectedRow(row); + fetchPoDetail(selectedPoId.toString(), true, row.id); } console.log(res); }, 200); @@ -539,57 +548,24 @@ const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { doSubmit(); } }, - [polInputList, row, rowIndex, dnFormProps, selectedPoId, fetchPoDetail, t], + [dnQtyInput, row, dnFormProps, selectedPoId, fetchPoDetail, t, lotNoInput], ); - const handleChange = useCallback(debounce((e: React.ChangeEvent) => { - const raw = e.target.value; - const id = e.target.id - // const temp = polInputList - switch (id) { - case "lotNo": - if (raw.trim() === '') { - polInputList[rowIndex].lotNo = ''; - break; - } - polInputList[rowIndex].lotNo = raw.trim(); - break; - case "dnQty": - // Allow empty input - const trimmed = raw.trim(); - - if (trimmed === "") { - polInputList[rowIndex].dnQty = 0; - break; - } - /* - // Keep digits only - const cleaned = raw.replace(/[^\d]/g, ''); - if (cleaned === '') { - // If the user typed only non-digits, keep previous value - break; - } -*/ - const parsed = Number(trimmed); - - // 不是合法數字(例如 "abc") - if (!Number.isFinite(parsed)) { - polInputList[rowIndex].dnQty = 0; // 或保留舊值,看你需求 - break; - } - - polInputList[rowIndex].dnQty = parsed; // 這裡允許小數,先存起來 - break; - default: - break; - } - // setPolInputList(() => temp) - }, 300), [rowIndex]); + const syncRowInputToParent = useCallback((lotNo: string, dnQty: string) => { + setPolInputList((prev) => { + const current = prev[row.id] ?? { lotNo: "", dnQty: "" }; + if (current.lotNo === lotNo && current.dnQty === dnQty) return prev; + return { + ...prev, + [row.id]: { lotNo, dnQty }, + }; + }); + }, [row.id]); // const [focusField, setFocusField] = useState(); // 本批收貨數量(訂單單位): 使用者在該行輸入的 dnQty - const batchPurchaseQty = polInputList[rowIndex]?.dnQty ?? 0; + const batchPurchaseQty = Number(dnQtyInput.trim()) || 0; // 已來貨總數(庫存單位): 同一 POL 底下所有 stock_in_line.acceptedQty 的合計 const totalStockReceived = row.stockInLine @@ -686,8 +662,9 @@ const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { label="輸入貨品批號" type="text" // Use type="text" to allow validation in the change handler variant="outlined" - defaultValue={polInputList[rowIndex]?.lotNo ?? ''} - onChange={handleChange} + value={lotNoInput} + onChange={(e) => setLotNoInput(e.target.value)} + onBlur={() => syncRowInputToParent(lotNoInput, dnQtyInput)} // onFocus={(e) => {setFocusField(e.target as HTMLInputElement);}} /> @@ -697,8 +674,9 @@ const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { label="此批送貨數量" type="text" // Use type="text" to allow validation in the change handler variant="outlined" - defaultValue={polInputList[rowIndex]?.dnQty ?? ''} - onChange={handleChange} + value={dnQtyInput} + onChange={(e) => setDnQtyInput(e.target.value)} + onBlur={() => syncRowInputToParent(lotNoInput, dnQtyInput)} InputProps={{ inputProps: { min: 0, // Optional: set a minimum value