"use client"; import SearchBox, { Criterion } from "../SearchBox"; import { useCallback, useMemo, useState, useEffect, useRef } from "react"; import { useTranslation } from "react-i18next"; import SearchResults, { Column } from "../SearchResults/index"; import { StockTransactionResponse, SearchStockTransactionRequest } from "@/app/api/stockTake/actions"; import { decimalFormatter } from "@/app/utils/formatUtil"; import { Stack, Box } from "@mui/material"; import { searchStockTransactions } from "@/app/api/stockTake/actions"; interface Props { dataList: StockTransactionResponse[]; } type SearchQuery = { itemCode?: string; itemName?: string; type?: string; startDate?: string; endDate?: string; }; // 扩展类型以包含计算字段 interface ExtendedStockTransaction extends StockTransactionResponse { formattedDate: string; inQty: number; outQty: number; balanceQty: number; } const SearchPage: React.FC = ({ dataList: initialDataList }) => { const { t } = useTranslation("inventory"); // 添加数据状态 const [dataList, setDataList] = useState(initialDataList); const [loading, setLoading] = useState(false); const [filterArgs, setFilterArgs] = useState>({}); const isInitialMount = useRef(true); // 添加分页状态 const [page, setPage] = useState(0); const [pageSize, setPageSize] = useState(10); const [pagingController, setPagingController] = useState({ pageNum: 1, pageSize: 10 }); const [hasSearchQuery, setHasSearchQuery] = useState(false); const [totalCount, setTotalCount] = useState(initialDataList.length); const processedData = useMemo(() => { // 按日期和 itemId 排序 - 优先使用 date 字段,如果没有则使用 transactionDate const sorted = [...dataList].sort((a, b) => { // 优先使用 date 字段,如果没有则使用 transactionDate 的日期部分 const getDateValue = (item: StockTransactionResponse): number => { if (item.date) { return new Date(item.date).getTime(); } if (item.transactionDate) { if (Array.isArray(item.transactionDate)) { const [year, month, day] = item.transactionDate; return new Date(year, month - 1, day).getTime(); } else { return new Date(item.transactionDate).getTime(); } } return 0; }; const dateA = getDateValue(a); const dateB = getDateValue(b); if (dateA !== dateB) return dateA - dateB; // 从旧到新排序 return a.itemId - b.itemId; }); // 计算每个 item 的累计余额 const balanceMap = new Map(); // itemId -> balance const processed: ExtendedStockTransaction[] = []; sorted.forEach((item) => { const currentBalance = balanceMap.get(item.itemId) || 0; let newBalance = currentBalance; // 根据类型计算余额 if (item.transactionType === "IN") { newBalance = currentBalance + item.qty; } else if (item.transactionType === "OUT") { newBalance = currentBalance - item.qty; } balanceMap.set(item.itemId, newBalance); // 格式化日期 - 优先使用 date 字段 let formattedDate = ""; if (item.date) { // 如果 date 是字符串格式 "yyyy-MM-dd" const date = new Date(item.date); if (!isNaN(date.getTime())) { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, "0"); const day = String(date.getDate()).padStart(2, "0"); formattedDate = `${year}-${month}-${day}`; } } else if (item.transactionDate) { // 回退到 transactionDate if (Array.isArray(item.transactionDate)) { const [year, month, day] = item.transactionDate; formattedDate = `${year}-${String(month).padStart(2, "0")}-${String(day).padStart(2, "0")}`; } else if (typeof item.transactionDate === 'string') { const date = new Date(item.transactionDate); if (!isNaN(date.getTime())) { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, "0"); const day = String(date.getDate()).padStart(2, "0"); formattedDate = `${year}-${month}-${day}`; } } else { const date = new Date(item.transactionDate); if (!isNaN(date.getTime())) { const year = date.getFullYear(); const month = String(date.getMonth() + 1).padStart(2, "0"); const day = String(date.getDate()).padStart(2, "0"); formattedDate = `${year}-${month}-${day}`; } } } processed.push({ ...item, formattedDate, inQty: item.transactionType === "IN" ? item.qty : 0, outQty: item.transactionType === "OUT" ? item.qty : 0, balanceQty: item.balanceQty ? item.balanceQty : newBalance, }); }); return processed; }, [dataList]); // 修复:使用 processedData 初始化 filteredList const [filteredList, setFilteredList] = useState(processedData); // 当 processedData 变化时更新 filteredList(不更新 pagingController,避免循环) useEffect(() => { setFilteredList(processedData); setTotalCount(processedData.length); // 只在初始加载时设置 pageSize if (isInitialMount.current && processedData.length > 0) { setPageSize("all"); setPagingController(prev => ({ ...prev, pageSize: processedData.length })); setPage(0); isInitialMount.current = false; } }, [processedData]); // API 调用函数(参考 PoSearch 的实现) // API 调用函数(参考 PoSearch 的实现) const newPageFetch = useCallback( async ( pagingController: Record, filterArgs: Record, ) => { setLoading(true); try { // 处理空字符串,转换为 null const itemCode = filterArgs.itemCode?.trim() || null; const itemName = filterArgs.itemName?.trim() || null; // 验证:至少需要 itemCode 或 itemName if (!itemCode && !itemName) { console.warn("Search requires at least itemCode or itemName"); setDataList([]); setTotalCount(0); return; } const params: SearchStockTransactionRequest = { itemCode: itemCode, itemName: itemName, type: filterArgs.type?.trim() || null, startDate: filterArgs.startDate || null, endDate: filterArgs.endDate || null, pageNum: pagingController.pageNum - 1 || 0, pageSize: pagingController.pageSize || 100, }; console.log("Search params:", params); // 添加调试日志 const res = await searchStockTransactions(params); console.log("Search response:", res); // 添加调试日志 if (res && Array.isArray(res)) { setDataList(res); } else { console.error("Invalid response format:", res); setDataList([]); } } catch (error) { console.error("Fetch error:", error); setDataList([]); } finally { setLoading(false); } }, [], ); // 使用 useRef 来存储上一次的值,避免不必要的 API 调用 const prevPagingControllerRef = useRef(pagingController); const prevFilterArgsRef = useRef(filterArgs); const hasSearchedRef = useRef(false); // 当 filterArgs 或 pagingController 变化时调用 API(只在真正变化时调用) useEffect(() => { // 检查是否有有效的搜索条件 const hasValidSearch = filterArgs.itemCode || filterArgs.itemName; if (!hasValidSearch) { // 如果没有有效搜索条件,只更新 ref,不调用 API if (isInitialMount.current) { isInitialMount.current = false; } prevFilterArgsRef.current = filterArgs; return; } // 检查是否真的变化了 const pagingChanged = prevPagingControllerRef.current.pageNum !== pagingController.pageNum || prevPagingControllerRef.current.pageSize !== pagingController.pageSize; const filterChanged = JSON.stringify(prevFilterArgsRef.current) !== JSON.stringify(filterArgs); // 如果是第一次有效搜索,或者条件/分页发生变化,则调用 API if (!hasSearchedRef.current || pagingChanged || filterChanged) { newPageFetch(pagingController, filterArgs); prevPagingControllerRef.current = pagingController; prevFilterArgsRef.current = filterArgs; hasSearchedRef.current = true; isInitialMount.current = false; } }, [newPageFetch, pagingController, filterArgs]); // 分页处理函数 const handleChangePage = useCallback((event: unknown, newPage: number) => { setPage(newPage); setPagingController(prev => ({ ...prev, pageNum: newPage + 1 })); }, []); const handleChangeRowsPerPage = useCallback((event: React.ChangeEvent) => { const newSize = parseInt(event.target.value, 10); if (newSize === -1) { setPageSize("all"); setPagingController(prev => ({ ...prev, pageSize: filteredList.length, pageNum: 1 })); } else if (!isNaN(newSize)) { setPageSize(newSize); setPagingController(prev => ({ ...prev, pageSize: newSize, pageNum: 1 })); } setPage(0); }, [filteredList.length]); const searchCriteria: Criterion[] = useMemo( () => [ { label: t("Item Code"), paramName: "itemCode", type: "text", }, { label: t("Item Name"), paramName: "itemName", type: "text", }, { label: t("Type"), paramName: "type", type: "text", }, { label: t("Start Date"), paramName: "startDate", type: "date", }, { label: t("End Date"), paramName: "endDate", type: "date", }, ], [t], ); const columns = useMemo[]>( () => [ { name: "formattedDate" as keyof ExtendedStockTransaction, label: t("Date"), align: "left", }, { name: "itemCode" as keyof ExtendedStockTransaction, label: t("Item-lotNo"), align: "left", renderCell: (item) => ( {item.itemCode || "-"} {item.itemName || "-"} {item.lotNo || "-"} ), }, { name: "inQty" as keyof ExtendedStockTransaction, label: t("In Qty"), align: "left", type: "decimal", renderCell: (item) => ( <>{item.inQty > 0 ? decimalFormatter.format(item.inQty) : ""} ), }, { name: "outQty" as keyof ExtendedStockTransaction, label: t("Out Qty"), align: "left", type: "decimal", renderCell: (item) => ( <>{item.outQty > 0 ? decimalFormatter.format(item.outQty) : ""} ), }, { name: "balanceQty" as keyof ExtendedStockTransaction, label: t("Balance Qty"), align: "left", type: "decimal", }, { name: "type", label: t("Type"), align: "left", renderCell: (item) => { if (!item.type) return "-"; return t(item.type.toLowerCase()); }, }, { name: "status", label: t("Status"), align: "left", renderCell: (item) => { if (!item.status) return "-"; return t(item.status.toLowerCase()); }, }, ], [t], ); const handleSearch = useCallback((query: Record) => { // 检查是否有搜索条件 const itemCode = query.itemCode?.trim(); const itemName = query.itemName?.trim(); const type = query.type?.trim(); const startDate = query.startDate === "Invalid Date" ? "" : query.startDate; const endDate = query.endDate === "Invalid Date" ? "" : query.endDate; // 验证:至少需要 itemCode 或 itemName if (!itemCode && !itemName) { // 可以显示提示信息 console.warn("Please enter at least Item Code or Item Name"); return; } const hasQuery = !!(itemCode || itemName || type || startDate || endDate); setHasSearchQuery(hasQuery); // 更新 filterArgs,触发 useEffect 调用 API setFilterArgs({ itemCode: itemCode || undefined, itemName: itemName || undefined, type: type || undefined, startDate: startDate || undefined, endDate: endDate || undefined, }); // 重置分页 setPage(0); setPagingController(prev => ({ ...prev, pageNum: 1 })); }, []); const handleReset = useCallback(() => { setHasSearchQuery(false); // 重置 filterArgs,触发 useEffect 调用 API setFilterArgs({}); setPage(0); setPagingController(prev => ({ ...prev, pageNum: 1 })); }, []); // 计算实际显示的 items(分页) const paginatedItems = useMemo(() => { if (pageSize === "all") { return filteredList; } const actualPageSize = typeof pageSize === 'number' ? pageSize : 10; const startIndex = page * actualPageSize; const endIndex = startIndex + actualPageSize; return filteredList.slice(startIndex, endIndex); }, [filteredList, page, pageSize]); // 计算传递给 SearchResults 的 pageSize(确保在选项中) const actualPageSizeForTable = useMemo(() => { if (pageSize === "all") { return filteredList.length; } const size = typeof pageSize === 'number' ? pageSize : 10; // 如果 size 不在标准选项中,使用 "all" 模式 if (![10, 25, 100].includes(size)) { return filteredList.length; } return size; }, [pageSize, filteredList.length]); return ( <> {loading && {t("Loading...")}} items={paginatedItems} columns={columns} pagingController={{ ...pagingController, pageSize: actualPageSizeForTable }} setPagingController={setPagingController} totalCount={totalCount} isAutoPaging={false} /> ); }; export default SearchPage;