Kaynağa Gözat

update do search

MergeProblem1
CANCERYS\kw093 2 hafta önce
ebeveyn
işleme
483577ed0d
2 değiştirilmiş dosya ile 378 ekleme ve 192 silme
  1. +103
    -11
      src/app/api/do/actions.tsx
  2. +275
    -181
      src/components/DoSearch/DoSearch.tsx

+ 103
- 11
src/app/api/do/actions.tsx Dosyayı Görüntüle

@@ -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<DoSearchAll[]>(`${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<DoSearchLiteResponse> {
// 构建请求体
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<DoSearchLiteResponse>(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<DoSearchAll[]> {
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<DoSearchAll[]> {
// 使用一个很大的 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<DoSearchLiteResponse>(url, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(requestBody),
});

return data.records;
}

+ 275
- 181
src/components/DoSearch/DoSearch.tsx Dosyayı Görüntüle

@@ -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<Props> = ({filterArgs, searchQuery, onDeliveryOrderSear
useState<GridRowSelectionModel>([]);

const [searchAllDos, setSearchAllDos] = useState<DoSearchAll[]>([]);
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<HTMLInputElement>) => {
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<SearchBoxInputs>({
code: "",
@@ -119,34 +99,24 @@ const DoSearch: React.FC<Props> = ({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<SearchParamNames>[] = 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<Props> = ({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<Props> = ({filterArgs, searchQuery, onDeliveryOrderSear
}
router.push(`/do/edit?id=${doResult.id}`);
},
[router],
[router, currentSearchParams],
);

const validationTest = useCallback(
(
newRow: GridRowModel<DoRow>,
// 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<Props> = ({filterArgs, searchQuery, onDeliveryOrderSear

const columns = useMemo<GridColDef[]>(
() => [
// {
// name: "id",
// label: t("Details"),
// onClick: onDetailClick,
// buttonIcon: <EditNote />,
// },
{
field: "id",
headerName: t("Details"),
@@ -240,7 +199,6 @@ const DoSearch: React.FC<Props> = ({filterArgs, searchQuery, onDeliveryOrderSear
headerName: t("Supplier Name"),
flex: 1,
},
{
field: "orderDate",
headerName: t("Order Date"),
@@ -250,9 +208,7 @@ const DoSearch: React.FC<Props> = ({filterArgs, searchQuery, onDeliveryOrderSear
? arrayToDateString(params.row.orderDate)
: "N/A";
},
},
{
field: "estimatedArrivalDate",
headerName: t("Estimated Arrival"),
@@ -272,7 +228,7 @@ const DoSearch: React.FC<Props> = ({filterArgs, searchQuery, onDeliveryOrderSear
},
},
],
[t, arrayToDateString],
[t, arrayToDateString, onDetailClick],
);

const onSubmit = useCallback<SubmitHandler<CreateConsoDoInput>>(
@@ -280,35 +236,24 @@ const DoSearch: React.FC<Props> = ({filterArgs, searchQuery, onDeliveryOrderSear
const hasErrors = false;
console.log(errors);
},
[],
[errors],
);
const onSubmitError = useCallback<SubmitErrorHandler<CreateConsoDoInput>>(
(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<Props> = ({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<Props> = ({filterArgs, searchQuery, onDeliveryOrderSear
}
}
}, [handleSearch]);

const debouncedSearch = useCallback((query: SearchBoxInputs) => {
if (searchTimeout) {
clearTimeout(searchTimeout);
@@ -385,98 +336,254 @@ const DoSearch: React.FC<Props> = ({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<HTMLInputElement>) => {
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: `
<div>
<p>${t("Selected Shop(s): ")}${searchAllDos.length}</p>
<p>${t("Selected Item(s): ")}${totalDeliveryOrderLines}</p>
</div>
`,
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: `
<div>
<p>${t("Selected Shop(s): ")}${allMatchingDos.length}</p>
<p style="font-size: 0.9em; color: #666; margin-top: 8px;">
${currentSearchParams.code ? `${t("Code")}: ${currentSearchParams.code} ` : ""}
${currentSearchParams.shopName ? `${t("Shop Name")}: ${currentSearchParams.shopName} ` : ""}
${currentSearchParams.estimatedArrivalDate ? `${t("Estimated Arrival")}: ${currentSearchParams.estimatedArrivalDate} ` : ""}
${status ? `${t("Status")}: ${status} ` : ""}
</p>
</div>
`,
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<Props> = ({filterArgs, searchQuery, onDeliveryOrderSear
alignItems="end"
>
<Stack spacing={2} direction="row">
{/*<Button
name="submit"
variant="contained"
// startIcon={<Check />}
type="submit"
>
{t("Create")}
</Button>*/}
{hasSearched && hasResults && (
<Button
name="batch_release"
@@ -517,22 +616,18 @@ const DoSearch: React.FC<Props> = ({filterArgs, searchQuery, onDeliveryOrderSear
{t("Batch Release")}
</Button>
)}

</Stack>
</Grid>
</Grid>

<SearchBox
criteria={searchCriteria}

onSearch={handleSearch}

onReset={onReset}
/>

<StyledDataGrid
rows={pagedRows}
rows={searchAllDos}
columns={columns}
checkboxSelection
rowSelectionModel={rowSelectionModel}
@@ -547,17 +642,16 @@ const DoSearch: React.FC<Props> = ({filterArgs, searchQuery, onDeliveryOrderSear
/>
<TablePagination
component="div"
count={searchAllDos.length}
page={(pagingController.pageNum - 1)}
rowsPerPage={pagingController.pageSize}
onPageChange={handlePageChange}
onRowsPerPageChange={handlePageSizeChange}
rowsPerPageOptions={[10, 25, 50]}
/>
component="div"
count={totalCount}
page={(pagingController.pageNum - 1)}
rowsPerPage={pagingController.pageSize}
onPageChange={handlePageChange}
onRowsPerPageChange={handlePageSizeChange}
rowsPerPageOptions={[10, 25, 50]}
/>

</Stack>

</FormProvider>
</>
);


Yükleniyor…
İptal
Kaydet