diff --git a/src/app/api/po/actions.ts b/src/app/api/po/actions.ts index afe937b..9379dbf 100644 --- a/src/app/api/po/actions.ts +++ b/src/app/api/po/actions.ts @@ -200,6 +200,21 @@ export const fetchPoInClient = cache(async (id: number) => { }); }); +export interface PurchaseOrderSummary { + id: number; + code: string; + status: string; + orderDate: string; + estimatedArrivalDate: string; + supplierName: string; + escalated: boolean; +} +export const fetchPoSummariesClient = cache(async (ids: number[]) => { + return serverFetchJson(`${BASE_API_URL}/po/summary`, { + next: { tags: ["po"] }, + }); +}); + export const fetchPoListClient = cache( async (queryParams?: Record) => { if (queryParams) { diff --git a/src/components/PoDetail/PoDetail.tsx b/src/components/PoDetail/PoDetail.tsx index e2a55fd..6a726f3 100644 --- a/src/components/PoDetail/PoDetail.tsx +++ b/src/components/PoDetail/PoDetail.tsx @@ -43,6 +43,7 @@ import { checkPolAndCompletePo, fetchPoInClient, fetchPoListClient, + fetchPoSummariesClient, startPo, } from "@/app/api/po/actions"; import { @@ -201,6 +202,7 @@ const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { purchaseOrder.pol || [], ); const [polInputList, setPolInputList] = useState([]) + const PO_DETAIL_SELECTION_KEY = "po-detail-selection"; useEffect(() => { setPolInputList( (purchaseOrder.pol ?? []).map(() => ({ @@ -209,7 +211,21 @@ const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { } as PolInputResult)) ); }, [purchaseOrder.pol]); - + useEffect(() => { + try { + const raw = sessionStorage.getItem("po-detail-selection"); + if (raw) { + const parsed = JSON.parse(raw) as { id: number; code: string; status: string; supplier: string | null }[]; + if (Array.isArray(parsed) && parsed.length > 0) { + setPoList(parsed as PoResult[]); + + sessionStorage.removeItem("po-detail-selection"); // 可选:用一次就删,避免下次从别处进还看到旧数据 + } + } + } catch (e) { + console.warn("sessionStorage getItem/parse failed", e); + } + }, []); const pathname = usePathname() const searchParams = useSearchParams(); @@ -227,6 +243,7 @@ const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { const router = useRouter(); const [poList, setPoList] = useState([]); + const [isPoListLoading, setIsPoListLoading] = useState(false); const [selectedPoId, setSelectedPoId] = useState(po.id); const [focusField, setFocusField] = useState(); @@ -240,15 +257,26 @@ const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { } }) const fetchPoList = useCallback(async () => { + setIsPoListLoading(true); try { if (selectedIdsParam) { - - const selectedIds = selectedIdsParam.split(',').map(id => parseInt(id)); - const promises = selectedIds.map(id => fetchPoInClient(id)); - const results = await Promise.all(promises); - setPoList(results.filter(Boolean)); + const MAX_IDS = 20; // 一次最多加载 20 个,防止卡死 + + const allIds = selectedIdsParam + .split(',') + .map(id => parseInt(id)) + .filter(id => !Number.isNaN(id)); + + const limitedIds = allIds.slice(0, MAX_IDS); + + if (allIds.length > MAX_IDS) { + console.warn( + `selectedIds too many (${allIds.length}), only loading first ${MAX_IDS}.` + ); + } + const result = await fetchPoSummariesClient(limitedIds); + setPoList(result as any); } else { - const result = await fetchPoListClient({ limit: 20, offset: 0 }); if (result && result.records) { setPoList(result.records); @@ -256,6 +284,8 @@ const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { } } catch (error) { console.error("Failed to fetch PO list:", error); + } finally { + setIsPoListLoading(false); } }, [selectedIdsParam]); @@ -311,11 +341,11 @@ const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { fetchPoDetail(currentPoId); } }, [currentPoId, fetchPoDetail]); - +/* useEffect(() => { fetchPoList(); }, [fetchPoList]); - +*/ useEffect(() => { if (currentPoId) { setSelectedPoId(parseInt(currentPoId)); @@ -490,12 +520,15 @@ const PoDetail: React.FC = ({ po, warehouse, printerCombo }) => { const highlightColor = (Number(receivedTotal.replace(/,/g, '')) <= 0) ? "red" : "inherit"; return ( <> + + *": { borderBottom: "unset" }, color: "black" }} onClick={() => changeStockInLines(row.id)} > + {/* = ({ po, warehouse, printerCombo }) => { {/* left side select po */} - - + + + + + {/* right side po info */} diff --git a/src/components/PoSearch/PoSearch.tsx b/src/components/PoSearch/PoSearch.tsx index a9d6b82..2cb2d7f 100644 --- a/src/components/PoSearch/PoSearch.tsx +++ b/src/components/PoSearch/PoSearch.tsx @@ -41,6 +41,7 @@ const PoSearch: React.FC = ({ const [filterArgs, setFilterArgs] = useState>({estimatedArrivalDate : dayjsToDateString(dayjs(), "input")}); const { t } = useTranslation(["purchaseOrder", "dashboard"]); const router = useRouter(); + const PO_DETAIL_SELECTION_KEY = "po-detail-selection"; const [pagingController, setPagingController] = useState( defaultPagingController, ); @@ -83,7 +84,18 @@ const PoSearch: React.FC = ({ (po: PoResult) => { setSelectedPoIds([]); setSelectAll(false); - router.push(`/po/edit?id=${po.id}&start=true&selectedIds=${po.id}`); + const listForDetail = [ + { id: po.id, code: po.code, status: po.status, supplier: po.supplier ?? null }, + ]; + try { + sessionStorage.setItem( + PO_DETAIL_SELECTION_KEY, + JSON.stringify(listForDetail), + ); + } catch (e) { + console.warn("sessionStorage setItem failed", e); + } + router.push(`/po/edit?id=${po.id}&start=true`); }, [router], ); @@ -111,12 +123,26 @@ const PoSearch: React.FC = ({ // navigate to PoDetail page const handleGoToPoDetail = useCallback(() => { - if (selectedPoIds.length > 0) { - const selectedIdsParam = selectedPoIds.join(','); - const firstPoId = selectedPoIds[0]; - router.push(`/po/edit?id=${firstPoId}&start=true&selectedIds=${selectedIdsParam}`); + if (selectedPoIds.length === 0) return; + + const selectedList = filteredPo.filter((p) => selectedPoIds.includes(p.id)); + const listForDetail = selectedList.map((p) => ({ + id: p.id, + code: p.code, + status: p.status, + supplier: p.supplier ?? null, + })); + + try { + sessionStorage.setItem("po-detail-selection", JSON.stringify(listForDetail)); + } catch (e) { + console.warn("sessionStorage setItem failed", e); } - }, [selectedPoIds, router]); + + const selectedIdsParam = selectedPoIds.join(","); + const firstPoId = selectedPoIds[0]; + router.push(`/po/edit?id=${firstPoId}&start=true&selectedIds=${selectedIdsParam}`); + }, [selectedPoIds, filteredPo, router]); const itemColumn = useCallback((value: string | undefined) => { if (!value) { diff --git a/src/i18n/zh/inventory.json b/src/i18n/zh/inventory.json index b0d5a32..1e2b135 100644 --- a/src/i18n/zh/inventory.json +++ b/src/i18n/zh/inventory.json @@ -11,6 +11,7 @@ "Variance %": "差異百分比", "fg": "成品", "Back to List": "返回列表", + "Start Stock Take Date": "盤點日期", "Record Status": "記錄狀態", "Stock take record status updated to not match": "盤點記錄狀態更新為數值不符", "available": "可用", diff --git a/src/i18n/zh/items.json b/src/i18n/zh/items.json index 288c7b8..d2043a0 100644 --- a/src/i18n/zh/items.json +++ b/src/i18n/zh/items.json @@ -9,6 +9,7 @@ "Edit Product / Material": "編輯產品 / 材料", "Product / Material": "產品 / 材料", "Product / Material Details": "產品 / 材料詳情", + "Qc items": "QC 項目", "Qc Category": "質檢模板", "Name": "名稱", diff --git a/src/i18n/zh/pickOrder.json b/src/i18n/zh/pickOrder.json index e7a3f00..0a9d55f 100644 --- a/src/i18n/zh/pickOrder.json +++ b/src/i18n/zh/pickOrder.json @@ -29,12 +29,12 @@ "Do you want to start?": "確定開始嗎?", "Start": "開始", "Pick Order Code(s)": "提料單編號", - "Delivery Order Code(s)": "送貨單編號", + "Delivery Order Code(s)": "提料單編號", "Start Success": "開始成功", "Truck Lance Code": "車牌號碼", "Pick Order Codes": "提料單編號", "Pick Order Lines": "提料單行數", - "Delivery Order Codes": "送貨單編號", + "Delivery Order Codes": "提料單編號", "Delivery Order Lines": "送貨單行數", "Lines Per Pick Order": "每提料單行數", "Pick Orders Details": "提料單詳情", @@ -394,9 +394,15 @@ "submitStockIn": "提交入庫", "not default warehosue": "不是默認倉庫", "printQty": "打印數量", + "Shop": "商店名稱", "warehouse": "倉庫", "Add Record": "添加記錄", "Clean Record": "清空記錄", + "Select": "選擇", + "Close": "關閉", + "Truck": "車輛", + "Date": "日期", + "Delivery Order Code": "送貨單編號", "Escalation Info": "升級信息", "Escalation Result": "升級結果", "acceptQty must not greater than": "接受數量不能大於", @@ -426,10 +432,17 @@ "No entries available": "該樓層未有需處理訂單", "Today": "是日", "Tomorrow": "翌日", + "No Stock Available": "沒有庫存可用", + "This lot is not available, please scan another lot.": "此批號不可用,請掃描其他批號。", "Day After Tomorrow": "後日", "Select Date": "請選擇日期", + "Search by Shop": "搜尋商店", + "Search by Truck": "搜尋貨車", "Print DN & Label": "列印提料單和送貨單標籤", "Print Label": "列印送貨單標籤", + "Not Yet Finished Released Do Pick Orders": "未完成提料單", + "Not yet finished released do pick orders": "未完成提料單", + "Released orders not yet completed - click lane to select and assign": "未完成提料單- 點擊貨車班次選擇並分配", "Ticket Release Table": "查看提貨情況", "Please take one pick order before printing the draft.": "請先從「撳單/提料單詳情」頁面下方選取提料單,再列印草稿。", "No released pick order records found.": "目前沒有可用的提料單。",