"use client"; import { PoResult } from "@/app/api/po"; import React, { useCallback, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; import { useRouter, useSearchParams } from "next/navigation"; import SearchBox, { Criterion } from "../SearchBox"; import SearchResults, { Column } from "../SearchResults"; import { EditNote } from "@mui/icons-material"; import { Backdrop, Button, CircularProgress, Grid, Tab, Tabs, TabsProps, Typography } from "@mui/material"; import QrModal from "../PoDetail/QrModal"; import { WarehouseResult } from "@/app/api/warehouse"; import NotificationIcon from "@mui/icons-material/NotificationImportant"; import { useSession } from "next-auth/react"; import { defaultPagingController } from "../SearchResults/SearchResults"; import { testing } from "@/app/api/po/actions"; import dayjs from "dayjs"; import { arrayToDateString, dayjsToDateString } from "@/app/utils/formatUtil"; import arraySupport from "dayjs/plugin/arraySupport"; import { Checkbox, Box } from "@mui/material"; import { NEXT_PUBLIC_API_URL } from "@/config/api"; import { clientAuthFetch } from "@/app/utils/clientAuthFetch"; dayjs.extend(arraySupport); type Props = { po: PoResult[]; warehouse: WarehouseResult[]; totalCount: number; }; type SearchQuery = Partial>; type SearchParamNames = keyof SearchQuery; // cal offset (pageSize) // cal limit (pageSize) const PoSearch: React.FC = ({ po, warehouse, totalCount: initTotalCount, }) => { const [selectedPoIds, setSelectedPoIds] = useState([]); const [selectAll, setSelectAll] = useState(false); const [filteredPo, setFilteredPo] = useState(po); 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, ); const [totalCount, setTotalCount] = useState(initTotalCount); const searchCriteria: Criterion[] = useMemo(() => { const searchCriteria: Criterion[] = [ { label: t("Supplier"), paramName: "supplier", type: "text" }, { label: t("PO No."), paramName: "code", type: "text" }, { label: t("Escalated"), paramName: "escalated", type: "select", options: [t("Escalated"), t("NotEscalated")], }, { label: t("Order Date"), label2: t("Order Date To"), paramName: "orderDate", type: "dateRange" }, { label: t("Status"), paramName: "status", type: "select-labelled", options: [ { label: t(`pending`), value: `pending` }, { label: t(`receiving`), value: `receiving` }, { label: t(`completed`), value: `completed` }, ], }, { label: t("ETA"), label2: t("ETA To"), paramName: "estimatedArrivalDate", type: "dateRange", preFilledValue: { from: dayjsToDateString(dayjs(), "input"), to: dayjsToDateString(dayjs(), "input"), }, }, ]; return searchCriteria; }, [t]); const onDetailClick = useCallback( (po: PoResult) => { setSelectedPoIds([]); setSelectAll(false); 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], ); const onDeleteClick = useCallback((po: PoResult) => {}, []); // handle single checkbox selection const handleSelectPo = useCallback((poId: number, checked: boolean) => { if (checked) { setSelectedPoIds(prev => [...prev, poId]); } else { setSelectedPoIds(prev => prev.filter(id => id !== poId)); } }, []); // 处理全选 const handleSelectAll = useCallback((checked: boolean) => { if (checked) { setSelectedPoIds(filteredPo.map(po => po.id)); setSelectAll(true); } else { setSelectedPoIds([]); setSelectAll(false); } }, [filteredPo]); // navigate to PoDetail page const handleGoToPoDetail = useCallback(() => { 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); } 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) { return "N/A" } const items = value.split(",") return items.map((item) => {item}) }, []) const columns = useMemo[]>( () => [ { name: "id" as keyof PoResult, label: "", renderCell: (params) => ( handleSelectPo(params.id, e.target.checked)} onClick={(e) => e.stopPropagation()} /> ), width: 60, }, { name: "id", label: t("Details"), onClick: onDetailClick, buttonIcon: , }, { name: "code", label: `${t("PO No.")} ${t("&")}\n${t("Supplier")}`, renderCell: (params) => { return <>{params.code}
{params.supplier} }, }, { name: "orderDate", label: `${t("Order Date")} ${t("&")}\n${t("ETA")}`, renderCell: (params) => { // return ( // dayjs(params.estimatedArrivalDate) // .add(-1, "month") // .format(OUTPUT_DATE_FORMAT) // ); return <>{arrayToDateString(params.orderDate)}
{arrayToDateString(params.estimatedArrivalDate)} }, }, // { // name: "itemDetail", // label: t("Item Detail"), // renderCell: (params) => { // if (!params.itemDetail) { // return "N/A" // } // const items = params.itemDetail.split(",") // return items.map((item) => {item}) // }, // }, { name: "itemCode", label: t("Item Code"), renderCell: (params) => { return itemColumn(params.itemCode); }, }, { name: "itemName", label: t("Item Name"), renderCell: (params) => { return itemColumn(params.itemName); }, }, { name: "itemQty", label: t("Item Qty"), renderCell: (params) => { return itemColumn(params.itemQty); }, }, { name: "itemSumAcceptedQty", label: t("Item Accepted Qty"), renderCell: (params) => { return itemColumn(params.itemSumAcceptedQty); }, }, { name: "itemUom", label: t("Item Purchase UoM"), renderCell: (params) => { return itemColumn(params.itemUom); }, }, { name: "status", label: t("Status"), renderCell: (params) => { return t(`${params.status.toLowerCase()}`); }, }, { name: "escalated", label: t("Escalated"), renderCell: (params) => { // console.log(params.escalated); return params.escalated ? ( ) : undefined; }, }, ], [selectedPoIds, handleSelectPo, onDetailClick, t], // only keep necessary dependencies ); const onReset = useCallback(() => { setFilteredPo(po); }, [po]); const [isOpenScanner, setOpenScanner] = useState(false); const [autoSyncStatus, setAutoSyncStatus] = useState(null); const [isM18LookupLoading, setIsM18LookupLoading] = useState(false); const autoSyncInProgressRef = React.useRef(false); const onOpenScanner = useCallback(() => { setOpenScanner(true); }, []); const onCloseScanner = useCallback(() => { setOpenScanner(false); }, []); const newPageFetch = useCallback( async ( pagingController: Record, filterArgs: Record, ) => { console.log(pagingController); console.log(filterArgs); const params = { ...pagingController, ...filterArgs, }; setAutoSyncStatus(null); const cleanedQuery: Record = {}; Object.entries(params).forEach(([k, v]) => { if (v === undefined || v === null) return; if (typeof v === "string" && (v as string).trim() === "") return; cleanedQuery[k] = String(v); }); const baseListResp = await clientAuthFetch( `${NEXT_PUBLIC_API_URL}/po/list?${new URLSearchParams(cleanedQuery).toString()}`, { method: "GET" }, ); if (!baseListResp.ok) { throw new Error(`PO list fetch failed: ${baseListResp.status}`); } const res = await baseListResp.json(); if (!res) return; if (res.records && res.records.length > 0) { setFilteredPo(res.records); setTotalCount(res.total); return; } const searchedCodeRaw = (filterArgs as any)?.code; const searchedCode = typeof searchedCodeRaw === "string" ? searchedCodeRaw.trim() : ""; const shouldAutoSyncFromM18 = searchedCode.length > 14 && (searchedCode.startsWith("PP") || searchedCode.startsWith("PF")); if (!shouldAutoSyncFromM18 || autoSyncInProgressRef.current) { setFilteredPo(res.records); setTotalCount(res.total); return; } try { autoSyncInProgressRef.current = true; setIsM18LookupLoading(true); setAutoSyncStatus("正在從M18找尋PO..."); const syncResp = await clientAuthFetch( `${NEXT_PUBLIC_API_URL}/m18/test/po-by-code?code=${encodeURIComponent( searchedCode, )}`, { method: "GET" }, ); if (!syncResp.ok) { throw new Error(`M18 sync failed: ${syncResp.status}`); } let syncJson: any = null; try { syncJson = await syncResp.json(); } catch { // Some endpoints may respond with plain text const txt = await syncResp.text(); syncJson = { raw: txt }; } const syncOk = Boolean(syncJson?.totalSuccess && syncJson.totalSuccess > 0); if (syncOk) { setAutoSyncStatus("成功找到PO"); const listResp = await clientAuthFetch( `${NEXT_PUBLIC_API_URL}/po/list?${new URLSearchParams( cleanedQuery, ).toString()}`, { method: "GET" }, ); if (listResp.ok) { const listJson = await listResp.json(); setFilteredPo(listJson.records ?? []); setTotalCount(listJson.total ?? 0); setAutoSyncStatus("成功找到PO"); return; } setAutoSyncStatus("找不到PO"); } else { setAutoSyncStatus("找不到PO"); } // Ensure UI updates even if sync didn't change results setFilteredPo(res.records); setTotalCount(res.total ?? 0); } catch (e) { console.error("Auto sync error:", e); setAutoSyncStatus("找不到PO"); setFilteredPo(res.records); setTotalCount(res.total ?? 0); } finally { setIsM18LookupLoading(false); autoSyncInProgressRef.current = false; } }, [], ); useEffect(() => { console.log(filteredPo) }, [filteredPo]) useEffect(() => { newPageFetch(pagingController, filterArgs); }, [newPageFetch, pagingController, filterArgs]); // when filteredPo changes, update select all state useEffect(() => { if (filteredPo.length > 0 && selectedPoIds.length === filteredPo.length) { setSelectAll(true); } else { setSelectAll(false); } }, [filteredPo, selectedPoIds]); return ( <> {t("Purchase Receipt")} <> { if (isM18LookupLoading) return; console.log(query); const code = typeof query.code === "string" ? query.code.trim() : ""; if (code) { // When PO code is provided, ignore other search criteria (especially date ranges). setFilterArgs({ code }); } else { setFilterArgs({ code: query.code, supplier: query.supplier, status: query.status === "All" ? "" : query.status, escalated: query.escalated === "All" ? undefined : query.escalated === t("Escalated"), estimatedArrivalDate: query.estimatedArrivalDate === "Invalid Date" ? "" : query.estimatedArrivalDate, estimatedArrivalDateTo: query.estimatedArrivalDateTo === "Invalid Date" ? "" : query.estimatedArrivalDateTo, orderDate: query.orderDate === "Invalid Date" ? "" : query.orderDate, orderDateTo: query.orderDateTo === "Invalid Date" ? "" : query.orderDateTo, }); } setSelectedPoIds([]); // reset selected po ids setSelectAll(false); // reset select all }} onReset={onReset} /> {autoSyncStatus ? ( {autoSyncStatus} ) : null} items={filteredPo} columns={columns} pagingController={pagingController} setPagingController={setPagingController} totalCount={totalCount} isAutoPaging={false} /> {/* add select all and view selected button */} theme.zIndex.modal + 1, flexDirection: "column", gap: 1 }} > 正在從M18找尋PO... ); }; export default PoSearch;