From 483577ed0d2a72fdce27679d1a07591ef5432d31 Mon Sep 17 00:00:00 2001 From: "CANCERYS\\kw093" Date: Mon, 19 Jan 2026 22:50:47 +0800 Subject: [PATCH] update do search --- src/app/api/do/actions.tsx | 114 ++++++- src/components/DoSearch/DoSearch.tsx | 456 ++++++++++++++++----------- 2 files changed, 378 insertions(+), 192 deletions(-) diff --git a/src/app/api/do/actions.tsx b/src/app/api/do/actions.tsx index 497122b..1e22eab 100644 --- a/src/app/api/do/actions.tsx +++ b/src/app/api/do/actions.tsx @@ -44,13 +44,17 @@ export interface DoSearchAll { id: number; code: string; status: string; - estimatedArrivalDate: string; - orderDate: string; + estimatedArrivalDate: number[]; + orderDate: number[]; supplierName: string; shopName: string; - deliveryOrderLines: DoDetailLine[]; -} + shopAddress?: string; +} +export interface DoSearchLiteResponse { + records: DoSearchAll[]; + total: number; +} export interface ReleaseDoRequest { id: number; } @@ -286,15 +290,72 @@ export const fetchDoDetail = cache(async (id: number) => { }); }); -export const fetchDoSearch = cache(async (code: string, shopName: string, status: string, orderStartDate: string, orderEndDate: string, estArrStartDate: string, estArrEndDate: string)=>{ - console.log(`${BASE_API_URL}/do/search-DO/${code}&${shopName}&${status}&${orderStartDate}&${orderEndDate}&${estArrStartDate}&${estArrEndDate}`); - return serverFetchJson(`${BASE_API_URL}/do/search-DO/${code}&${shopName}&${status}&${orderStartDate}&${orderEndDate}&${estArrStartDate}&${estArrEndDate}`,{ - method: "GET", - next: { tags: ["doSearch"] } +export async function fetchDoSearch( + code: string, + shopName: string, + status: string, + orderStartDate: string, + orderEndDate: string, + estArrStartDate: string, + estArrEndDate: string, + pageNum?: number, + pageSize?: number +): Promise { + // 构建请求体 + const requestBody: any = { + code: code || null, + shopName: shopName || null, + status: status || null, + estimatedArrivalDate: estArrStartDate || null, // 使用单个日期字段 + pageNum: pageNum || 1, + pageSize: pageSize || 10, + }; + + // 如果日期不为空,转换为 LocalDateTime 格式 + if (estArrStartDate) { + requestBody.estimatedArrivalDate = estArrStartDate; // 格式: "2026-01-19T00:00:00" + } else { + requestBody.estimatedArrivalDate = null; + } + + const url = `${BASE_API_URL}/do/search-do-lite`; + + const data = await serverFetchJson(url, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(requestBody), }); - -}); + return data; +} +export async function fetchDoSearchList( + code: string, + shopName: string, + status: string, + orderStartDate: string, + orderEndDate: string, + etaFrom: string, + etaTo: string, + page = 0, + size = 500 +): Promise { + const params = new URLSearchParams(); + + if (code) params.append("code", code); + if (shopName) params.append("shopName", shopName); + if (status) params.append("status", status); + if (orderStartDate) params.append("orderFrom", orderStartDate); + if (orderEndDate) params.append("orderTo", orderEndDate); + if (etaFrom) params.append("etaFrom", etaFrom); + if (etaTo) params.append("etaTo", etaTo); + + params.append("page", String(page)); + params.append("size", String(size)); + + const res = await fetch(`/api/delivery-order/search-do-list?${params.toString()}`); + const pageData = await res.json(); // Spring Page 结构 + return pageData.content; // 前端继续沿用你原来的 client-side 分页逻辑 +} export async function printDN(request: PrintDeliveryNoteRequest){ const params = new URLSearchParams(); params.append('doPickOrderId', request.doPickOrderId.toString()); @@ -371,4 +432,35 @@ export const check4FTrucksBatch = cache(async (doIds: number[]) => { }); }); +export async function fetchAllDoSearch( + code: string, + shopName: string, + status: string, + estArrStartDate: string +): Promise { + // 使用一个很大的 pageSize 来获取所有匹配的记录 + const requestBody: any = { + code: code || null, + shopName: shopName || null, + status: status || null, + estimatedArrivalDate: estArrStartDate || null, + pageNum: 1, + pageSize: 10000, // 使用一个很大的值来获取所有记录 + }; + if (estArrStartDate) { + requestBody.estimatedArrivalDate = estArrStartDate; + } else { + requestBody.estimatedArrivalDate = null; + } + + const url = `${BASE_API_URL}/do/search-do-lite`; + + const data = await serverFetchJson(url, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(requestBody), + }); + + return data.records; +} diff --git a/src/components/DoSearch/DoSearch.tsx b/src/components/DoSearch/DoSearch.tsx index e3282a7..600515d 100644 --- a/src/components/DoSearch/DoSearch.tsx +++ b/src/components/DoSearch/DoSearch.tsx @@ -1,7 +1,8 @@ "use client"; import { DoResult } from "@/app/api/do"; -import { DoSearchAll, fetchDoSearch, releaseDo ,startBatchReleaseAsync, getBatchReleaseProgress} from "@/app/api/do/actions"; +import { DoSearchAll, DoSearchLiteResponse, fetchDoSearch, fetchAllDoSearch, fetchDoSearchList, releaseDo ,startBatchReleaseAsync, getBatchReleaseProgress} from "@/app/api/do/actions"; + import { useRouter } from "next/navigation"; import React, { ForwardedRef, useCallback, useEffect, useMemo, useState } from "react"; import { useTranslation } from "react-i18next"; @@ -71,33 +72,12 @@ const DoSearch: React.FC = ({filterArgs, searchQuery, onDeliveryOrderSear useState([]); const [searchAllDos, setSearchAllDos] = useState([]); + const [totalCount, setTotalCount] = useState(0); const [pagingController, setPagingController] = useState({ pageNum: 1, pageSize: 10, }); - - const handlePageChange = useCallback((event: unknown, newPage: number) => { - const newPagingController = { - ...pagingController, - pageNum: newPage + 1, - }; - setPagingController(newPagingController); - },[pagingController]); - - const handlePageSizeChange = useCallback((event: React.ChangeEvent) => { - const newPageSize = parseInt(event.target.value, 10); - const newPagingController = { - pageNum: 1, - pageSize: newPageSize, - }; - setPagingController(newPagingController); - }, []); - - const pagedRows = useMemo(() => { - const start = (pagingController.pageNum - 1) * pagingController.pageSize; - return searchAllDos.slice(start, start + pagingController.pageSize); - }, [searchAllDos, pagingController]); const [currentSearchParams, setCurrentSearchParams] = useState({ code: "", @@ -119,34 +99,24 @@ const DoSearch: React.FC = ({filterArgs, searchQuery, onDeliveryOrderSear const [hasSearched, setHasSearched] = useState(false); const [hasResults, setHasResults] = useState(false); - useEffect(() =>{ + // 当搜索条件变化时,重置到第一页 + useEffect(() => { setPagingController(p => ({ ...p, pageNum: 1, })); - }, [searchAllDos]); + }, [currentSearchParams.code, currentSearchParams.shopName, currentSearchParams.status, currentSearchParams.estimatedArrivalDate]); const searchCriteria: Criterion[] = useMemo( () => [ { label: t("Code"), paramName: "code", type: "text" }, -/* - { - label: t("Order Date From"), - label2: t("Order Date To"), - paramName: "orderDate", - type: "dateRange", - }, - */ { label: t("Shop Name"), paramName: "shopName", type: "text" }, - { label: t("Estimated Arrival"), - //label2: t("Estimated Arrival To"), paramName: "estimatedArrivalDate", type: "date", }, - { label: t("Status"), paramName: "status", @@ -164,12 +134,15 @@ const DoSearch: React.FC = ({filterArgs, searchQuery, onDeliveryOrderSear const onReset = useCallback(async () => { try { setSearchAllDos([]); + setTotalCount(0); setHasSearched(false); setHasResults(false); + setPagingController({ pageNum: 1, pageSize: 10 }); } catch (error) { console.error("Error: ", error); setSearchAllDos([]); + setTotalCount(0); } }, []); @@ -180,23 +153,15 @@ const DoSearch: React.FC = ({filterArgs, searchQuery, onDeliveryOrderSear } router.push(`/do/edit?id=${doResult.id}`); }, - [router], + [router, currentSearchParams], ); const validationTest = useCallback( ( newRow: GridRowModel, - // rowModel: GridRowSelectionModel ): EntryError => { const error: EntryError = {}; console.log(newRow); - // if (!newRow.lowerLimit) { - // error["lowerLimit"] = "lower limit cannot be null" - // } - // if (newRow.lowerLimit && newRow.upperLimit && newRow.lowerLimit > newRow.upperLimit) { - // error["lowerLimit"] = "lower limit should not be greater than upper limit" - // error["upperLimit"] = "lower limit should not be greater than upper limit" - // } return Object.keys(error).length > 0 ? error : undefined; }, [], @@ -204,12 +169,6 @@ const DoSearch: React.FC = ({filterArgs, searchQuery, onDeliveryOrderSear const columns = useMemo( () => [ - // { - // name: "id", - // label: t("Details"), - // onClick: onDetailClick, - // buttonIcon: , - // }, { field: "id", headerName: t("Details"), @@ -240,7 +199,6 @@ const DoSearch: React.FC = ({filterArgs, searchQuery, onDeliveryOrderSear headerName: t("Supplier Name"), flex: 1, }, - { field: "orderDate", headerName: t("Order Date"), @@ -250,9 +208,7 @@ const DoSearch: React.FC = ({filterArgs, searchQuery, onDeliveryOrderSear ? arrayToDateString(params.row.orderDate) : "N/A"; }, - }, - { field: "estimatedArrivalDate", headerName: t("Estimated Arrival"), @@ -272,7 +228,7 @@ const DoSearch: React.FC = ({filterArgs, searchQuery, onDeliveryOrderSear }, }, ], - [t, arrayToDateString], + [t, arrayToDateString, onDetailClick], ); const onSubmit = useCallback>( @@ -280,35 +236,24 @@ const DoSearch: React.FC = ({filterArgs, searchQuery, onDeliveryOrderSear const hasErrors = false; console.log(errors); }, - [], + [errors], ); const onSubmitError = useCallback>( (errors) => {}, [], ); + //SEARCH FUNCTION const handleSearch = useCallback(async (query: SearchBoxInputs) => { try { setCurrentSearchParams(query); - let orderStartDate = ""; - let orderEndDate = ""; let estArrStartDate = query.estimatedArrivalDate; - let estArrEndDate = query.estimatedArrivalDate; const time = "T00:00:00"; - //if(orderStartDate != ""){ - // orderStartDate = query.orderDate + time; - //} - //if(orderEndDate != ""){ - // orderEndDate = query.orderDateTo + time; - //} if(estArrStartDate != ""){ estArrStartDate = query.estimatedArrivalDate + time; } - if(estArrEndDate != ""){ - estArrEndDate = query.estimatedArrivalDate + time; - } let status = ""; if(query.status == "All"){ @@ -318,28 +263,33 @@ const DoSearch: React.FC = ({filterArgs, searchQuery, onDeliveryOrderSear status = query.status; } - const data = await fetchDoSearch( + // 调用新的 API,传入分页参数 + const response = await fetchDoSearch( query.code || "", query.shopName || "", status, - orderStartDate, - orderEndDate, + "", // orderStartDate - 不再使用 + "", // orderEndDate - 不再使用 estArrStartDate, - estArrEndDate + "", // estArrEndDate - 不再使用 + pagingController.pageNum, // 传入当前页码 + pagingController.pageSize // 传入每页大小 ); - setSearchAllDos(data); + setSearchAllDos(response.records); + setTotalCount(response.total); // 设置总记录数 setHasSearched(true); - setHasResults(data.length > 0); + setHasResults(response.records.length > 0); } catch (error) { console.error("Error: ", error); setSearchAllDos([]); + setTotalCount(0); setHasSearched(true); setHasResults(false); - } - }, []); + }, [pagingController]); + useEffect(() => { if (typeof window !== 'undefined') { const savedSearchParams = sessionStorage.getItem('doSearchParams'); @@ -373,6 +323,7 @@ const DoSearch: React.FC = ({filterArgs, searchQuery, onDeliveryOrderSear } } }, [handleSearch]); + const debouncedSearch = useCallback((query: SearchBoxInputs) => { if (searchTimeout) { clearTimeout(searchTimeout); @@ -385,98 +336,254 @@ const DoSearch: React.FC = ({filterArgs, searchQuery, onDeliveryOrderSear setSearchTimeout(timeout); }, [handleSearch, searchTimeout]); + // 分页变化时重新搜索 + const handlePageChange = useCallback((event: unknown, newPage: number) => { + const newPagingController = { + ...pagingController, + pageNum: newPage + 1, + }; + setPagingController(newPagingController); + // 如果已经搜索过,重新搜索 + if (hasSearched && currentSearchParams) { + // 使用新的分页参数重新搜索 + const searchWithNewPage = async () => { + try { + let estArrStartDate = currentSearchParams.estimatedArrivalDate; + const time = "T00:00:00"; + + if(estArrStartDate != ""){ + estArrStartDate = currentSearchParams.estimatedArrivalDate + time; + } + + let status = ""; + if(currentSearchParams.status == "All"){ + status = ""; + } + else{ + status = currentSearchParams.status; + } + + const response = await fetchDoSearch( + currentSearchParams.code || "", + currentSearchParams.shopName || "", + status, + "", + "", + estArrStartDate, + "", + newPagingController.pageNum, + newPagingController.pageSize + ); + + setSearchAllDos(response.records); + setTotalCount(response.total); + } catch (error) { + console.error("Error: ", error); + } + }; + searchWithNewPage(); + } + }, [pagingController, hasSearched, currentSearchParams]); + + const handlePageSizeChange = useCallback((event: React.ChangeEvent) => { + const newPageSize = parseInt(event.target.value, 10); + const newPagingController = { + pageNum: 1, // 改变每页大小时重置到第一页 + pageSize: newPageSize, + }; + setPagingController(newPagingController); + // 如果已经搜索过,重新搜索 + if (hasSearched && currentSearchParams) { + const searchWithNewPageSize = async () => { + try { + let estArrStartDate = currentSearchParams.estimatedArrivalDate; + const time = "T00:00:00"; + + if(estArrStartDate != ""){ + estArrStartDate = currentSearchParams.estimatedArrivalDate + time; + } + + let status = ""; + if(currentSearchParams.status == "All"){ + status = ""; + } + else{ + status = currentSearchParams.status; + } + + const response = await fetchDoSearch( + currentSearchParams.code || "", + currentSearchParams.shopName || "", + status, + "", + "", + estArrStartDate, + "", + 1, // 重置到第一页 + newPageSize + ); + + setSearchAllDos(response.records); + setTotalCount(response.total); + } catch (error) { + console.error("Error: ", error); + } + }; + searchWithNewPageSize(); + } + }, [hasSearched, currentSearchParams]); + const handleBatchRelease = useCallback(async () => { + try { + // 根据当前搜索条件获取所有匹配的记录(不分页) + let estArrStartDate = currentSearchParams.estimatedArrivalDate; + const time = "T00:00:00"; + + if(estArrStartDate != ""){ + estArrStartDate = currentSearchParams.estimatedArrivalDate + time; + } + + let status = ""; + if(currentSearchParams.status == "All"){ + status = ""; + } + else{ + status = currentSearchParams.status; + } + + // 显示加载提示 + const loadingSwal = Swal.fire({ + title: t("Loading"), + text: t("Fetching all matching records..."), + allowOutsideClick: false, + allowEscapeKey: false, + showConfirmButton: false, + didOpen: () => { + Swal.showLoading(); + } + }); - const totalDeliveryOrderLines = searchAllDos.reduce((sum, doItem) => { - return sum + (doItem.deliveryOrderLines?.length || 0); - }, 0); - - const result = await Swal.fire({ - icon: "question", - title: t("Batch Release"), - html: ` -
-

${t("Selected Shop(s): ")}${searchAllDos.length}

-

${t("Selected Item(s): ")}${totalDeliveryOrderLines}

-
- `, - showCancelButton: true, - confirmButtonText: t("Confirm"), - cancelButtonText: t("Cancel"), - confirmButtonColor: "#8dba00", - cancelButtonColor: "#F04438" - }); + // 获取所有匹配的记录 + const allMatchingDos = await fetchAllDoSearch( + currentSearchParams.code || "", + currentSearchParams.shopName || "", + status, + estArrStartDate + ); - if (result.isConfirmed) { - const idsToRelease = searchAllDos.map(d => d.id); - - try { - const startRes = await startBatchReleaseAsync({ ids: idsToRelease, userId: currentUserId ?? 1 }); - const jobId = startRes?.entity?.jobId; + Swal.close(); - if (!jobId) { - await Swal.fire({ icon: "error", title: t("Error"), text: t("Failed to start batch release") }); + if (allMatchingDos.length === 0) { + await Swal.fire({ + icon: "warning", + title: t("No Records"), + text: t("No matching records found for batch release."), + confirmButtonText: t("OK") + }); return; } - - - const progressSwal = Swal.fire({ - title: t("Releasing"), - text: "0% (0 / 0)", - allowOutsideClick: false, - allowEscapeKey: false, - showConfirmButton: false, - didOpen: () => { - Swal.showLoading(); - } - }); - - const timer = setInterval(async () => { + + // 显示确认对话框 + const result = await Swal.fire({ + icon: "question", + title: t("Batch Release"), + html: ` +
+

${t("Selected Shop(s): ")}${allMatchingDos.length}

+

+ + ${currentSearchParams.code ? `${t("Code")}: ${currentSearchParams.code} ` : ""} + ${currentSearchParams.shopName ? `${t("Shop Name")}: ${currentSearchParams.shopName} ` : ""} + ${currentSearchParams.estimatedArrivalDate ? `${t("Estimated Arrival")}: ${currentSearchParams.estimatedArrivalDate} ` : ""} + ${status ? `${t("Status")}: ${status} ` : ""} +

+
+ `, + showCancelButton: true, + confirmButtonText: t("Confirm"), + cancelButtonText: t("Cancel"), + confirmButtonColor: "#8dba00", + cancelButtonColor: "#F04438" + }); + + if (result.isConfirmed) { + const idsToRelease = allMatchingDos.map(d => d.id); + try { - const p = await getBatchReleaseProgress(jobId); - - const e = p?.entity || {}; - const total = e.total ?? 0; - const finished = e.finished ?? 0; - const percentage = total > 0 ? Math.round((finished / total) * 100) : 0; - - const textContent = document.querySelector('.swal2-html-container'); - if (textContent) { - textContent.textContent = `${percentage}% (${finished} / ${total})`; - } - - if (p.code === "FINISHED" || e.running === false) { - clearInterval(timer); - await new Promise(resolve => setTimeout(resolve, 500)); - Swal.close(); - - await Swal.fire({ - icon: "success", - title: t("Completed"), - text: t("Batch release completed successfully."), - confirmButtonText: t("Confirm"), - confirmButtonColor: "#8dba00" - }); - - if (currentSearchParams && Object.keys(currentSearchParams).length > 0) { - await handleSearch(currentSearchParams); + const startRes = await startBatchReleaseAsync({ ids: idsToRelease, userId: currentUserId ?? 1 }); + const jobId = startRes?.entity?.jobId; + + if (!jobId) { + await Swal.fire({ icon: "error", title: t("Error"), text: t("Failed to start batch release") }); + return; } - } - } catch (err) { - console.error("progress poll error:", err); + + const progressSwal = Swal.fire({ + title: t("Releasing"), + text: "0% (0 / 0)", + allowOutsideClick: false, + allowEscapeKey: false, + showConfirmButton: false, + didOpen: () => { + Swal.showLoading(); + } + }); + + const timer = setInterval(async () => { + try { + const p = await getBatchReleaseProgress(jobId); + + const e = p?.entity || {}; + const total = e.total ?? 0; + const finished = e.finished ?? 0; + const percentage = total > 0 ? Math.round((finished / total) * 100) : 0; + + const textContent = document.querySelector('.swal2-html-container'); + if (textContent) { + textContent.textContent = `${percentage}% (${finished} / ${total})`; + } + + if (p.code === "FINISHED" || e.running === false) { + clearInterval(timer); + await new Promise(resolve => setTimeout(resolve, 500)); + Swal.close(); + + await Swal.fire({ + icon: "success", + title: t("Completed"), + text: t("Batch release completed successfully."), + confirmButtonText: t("Confirm"), + confirmButtonColor: "#8dba00" + }); + + if (currentSearchParams && Object.keys(currentSearchParams).length > 0) { + await handleSearch(currentSearchParams); + } + } + } catch (err) { + console.error("progress poll error:", err); + } + }, 800); + } catch (error) { + console.error("Batch release error:", error); + await Swal.fire({ + icon: "error", + title: t("Error"), + text: t("An error occurred during batch release"), + confirmButtonText: t("OK") + }); } - }, 800); - } catch (error) { - console.error("Batch release error:", error); - await Swal.fire({ - icon: "error", - title: t("Error"), - text: t("An error occurred during batch release"), - confirmButtonText: t("OK") - }); - }} -}, [t, currentUserId, searchAllDos, currentSearchParams, handleSearch]); - + } + } catch (error) { + console.error("Error fetching all matching records:", error); + await Swal.fire({ + icon: "error", + title: t("Error"), + text: t("Failed to fetch matching records"), + confirmButtonText: t("OK") + }); + } + }, [t, currentUserId, currentSearchParams, handleSearch]); return ( <> @@ -500,14 +607,6 @@ const DoSearch: React.FC = ({filterArgs, searchQuery, onDeliveryOrderSear alignItems="end" > - {/**/} {hasSearched && hasResults && ( )} - - = ({filterArgs, searchQuery, onDeliveryOrderSear /> + component="div" + count={totalCount} + page={(pagingController.pageNum - 1)} + rowsPerPage={pagingController.pageSize} + onPageChange={handlePageChange} + onRowsPerPageChange={handlePageSizeChange} + rowsPerPageOptions={[10, 25, 50]} + /> - );