浏览代码

update

master
CANCERYS\kw093 2 天前
父节点
当前提交
3b2495744f
共有 3 个文件被更改,包括 241 次插入218 次删除
  1. +28
    -0
      src/app/api/settings/item/actions.ts
  2. +60
    -113
      src/components/PickOrderSearch/PickOrderSearch.tsx
  3. +153
    -105
      src/components/PickOrderSearch/newcreatitem.tsx

+ 28
- 0
src/app/api/settings/item/actions.ts 查看文件

@@ -61,6 +61,34 @@ export interface ItemCombo {
group?: string,
currentStockBalance?: number,
}
export interface ItemWithDetails {
id: number;
code: string;
name: string;
description?: string;
uomId: number;
uom: string;
uomDesc: string;
currentStockBalance: number;
}
export const fetchItemsWithDetails = cache(async (searchParams?: Record<string, any>) => {
if (searchParams) {
const queryString = new URLSearchParams(searchParams).toString();
return serverFetchJson<RecordsRes<ItemWithDetails>>(
`${BASE_API_URL}/items/itemsWithDetails?${queryString}`,
{
next: { tags: ["items"] },
}
);
} else {
return serverFetchJson<RecordsRes<ItemWithDetails>>(
`${BASE_API_URL}/items/itemsWithDetails`,
{
next: { tags: ["items"] },
}
);
}
});

export const fetchAllItemsInClient = cache(async () => {
return serverFetchJson<ItemCombo[]>(`${BASE_API_URL}/items/consumables`, {


+ 60
- 113
src/components/PickOrderSearch/PickOrderSearch.tsx 查看文件

@@ -15,7 +15,7 @@ import {
import {
arrayToDayjs,
} from "@/app/utils/formatUtil";
import { Button, Grid, Stack, Tab, Tabs, TabsProps, Typography } from "@mui/material";
import { Button, Grid, Stack, Tab, Tabs, TabsProps, Typography, Box } from "@mui/material";
import PickOrders from "./PickOrders";
import ConsolidatedPickOrders from "./ConsolidatedPickOrders";
import PickExecution from "./PickExecution";
@@ -232,121 +232,68 @@ const PickOrderSearch: React.FC<Props> = ({ pickOrders }) => {
}, []);

return (
<>
<Stack
rowGap={2}
>
<Grid container>
<Grid item xs={8}>
<Typography variant="h4" marginInlineEnd={2}>
{t("Pick Order")}
</Typography>
<Box sx={{
height: '100vh', // Full viewport height
overflow: 'auto' // Single scrollbar for the whole page
}}>
{/* Header section */}
<Box sx={{
p: 2,
borderBottom: '1px solid #e0e0e0'
}}>
<Stack rowGap={2}>
<Grid container>
<Grid item xs={8}>
<Typography variant="h4" marginInlineEnd={2}>
{t("Pick Order")}
</Typography>
</Grid>
<Grid item xs={4} display="flex" justifyContent="end" alignItems="end">
<Button onClick={openCreateModal}>
{t("create")}
</Button>
{isOpenCreateModal &&
<CreatePickOrderModal
open={isOpenCreateModal}
onClose={closeCreateModal}
items={items}
/>
}
</Grid>
</Grid>
<Grid item xs={4} display="flex" justifyContent="end" alignItems="end">
<Button
onClick={openCreateModal}
>
{t("create")}
</Button>
{isOpenCreateModal &&
<CreatePickOrderModal
open={isOpenCreateModal}
onClose={closeCreateModal}
items={items}
/>}
</Grid>
</Grid>
</Stack>
{/*
<SearchBox
criteria={searchCriteria}
onSearch={(query) => {
console.log("SearchBox search triggered with query:", query);
setSearchQuery({ ...query });
// when tabIndex === 3, do not execute any search logic, only store query conditions
if (tabIndex !== 3) {
// only execute search logic when other tabs
setFilterArgs({ ...query });
setFilteredPickOrders(
pickOrders.filter((po) => {
const poTargetDateStr = arrayToDayjs(po.targetDate);

// safely check search conditions
const codeMatch = !query.code ||
po.code.toLowerCase().includes((query.code || "").toLowerCase());
const dateMatch = !query.targetDate ||
poTargetDateStr.isSame(query.targetDate) ||
poTargetDateStr.isAfter(query.targetDate);
const dateToMatch = !query.targetDateTo ||
poTargetDateStr.isSame(query.targetDateTo) ||
poTargetDateStr.isBefore(query.targetDateTo);
const itemsMatch = !query.items ||
Array.isArray(query.items) && (
intersectionWith(["All"], query.items).length > 0 ||
intersectionWith(
po.items?.map((item) => item.name) || [],
query.items,
).length > 0
);
const statusMatch = !query.status ||
query.status.toLowerCase() === "all" ||
po.status.toLowerCase().includes((query.status || "").toLowerCase());
const typeMatch = !query.type ||
query.type.toLowerCase() === "all" ||
po.type.toLowerCase().includes((query.type || "").toLowerCase());
</Stack>
</Box>

return codeMatch && dateMatch && dateToMatch && itemsMatch && statusMatch && typeMatch;
}),
);
}
// when tabIndex === 3, SearchBox's search will not trigger any filtering, only pass data to NewCreateItem
}}
onReset={() => {
console.log("SearchBox reset triggered");
setSearchQuery({});
if (tabIndex !== 3) {
onReset();
}
}}
/>
*/}
<Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable">
{/* Tabs section */}
<Box sx={{
borderBottom: '1px solid #e0e0e0'
}}>
<Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable">
<Tab label={t("Select Items")} iconPosition="end" />
<Tab label={t("Select Job Order Items")} iconPosition="end" />
<Tab label={t("Assign")} iconPosition="end" />
<Tab label={t("Release")} iconPosition="end" />
<Tab label={t("Pick Execution")} iconPosition="end" />
</Tabs>
</Box>

<Tab label={t("Select Items")} iconPosition="end" />
<Tab label={t("Select Job Order Items")} iconPosition="end" />
<Tab label={t("Assign")} iconPosition="end" />
<Tab label={t("Release")} iconPosition="end" />
<Tab label={t("Pick Execution")} iconPosition="end" />
{/*<Tab label={t("Pick Orders")} iconPosition="end" />*/}
{/*<Tab label={t("Consolidated Pick Orders")} iconPosition="end" />*/}
</Tabs>
{/*{tabIndex === 4 && (
<PickOrders
filteredPickOrders={filteredPickOrders}
filterArgs={filterArgs}
/>
)}*/}
{/*{tabIndex === 5 && <ConsolidatedPickOrders filterArgs={filterArgs} />}*/}
{tabIndex === 4 && <PickExecution filterArgs={filterArgs} />}
{tabIndex === 0 && (
<NewCreateItem
filterArgs={filterArgs}
searchQuery={searchQuery}
onPickOrderCreated={handlePickOrderCreated}
/>
)}
{tabIndex === 1 && <Jobcreatitem filterArgs={filterArgs} />}
{tabIndex === 2&& <AssignAndRelease filterArgs={filterArgs} />}
{tabIndex === 3 && <AssignTo filterArgs={filterArgs} />}
</>
{/* Content section - NO overflow: 'auto' here */}
<Box sx={{
p: 2
}}>
{tabIndex === 4 && <PickExecution filterArgs={filterArgs} />}
{tabIndex === 0 && (
<NewCreateItem
filterArgs={filterArgs}
searchQuery={searchQuery}
onPickOrderCreated={handlePickOrderCreated}
/>
)}
{tabIndex === 1 && <Jobcreatitem filterArgs={filterArgs} />}
{tabIndex === 2 && <AssignAndRelease filterArgs={filterArgs} />}
{tabIndex === 3 && <AssignTo filterArgs={filterArgs} />}
</Box>
</Box>
);
};



+ 153
- 105
src/components/PickOrderSearch/newcreatitem.tsx 查看文件

@@ -32,7 +32,7 @@ import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers";
import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
import dayjs from "dayjs";
import { Check, Search, RestartAlt } from "@mui/icons-material";
import { ItemCombo, fetchAllItemsInClient } from "@/app/api/settings/item/actions";
import { ItemCombo, fetchAllItemsInClient,fetchItemsWithDetails } from "@/app/api/settings/item/actions";
import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil";
import SearchResults, { Column } from "../SearchResults/SearchResults";
import { fetchJobOrderDetailByCode } from "@/app/api/jo/actions";
@@ -40,6 +40,7 @@ import SearchBox, { Criterion } from "../SearchBox";
import VerticalSearchBox from "./VerticalSearchBox";
import SearchResultsTable from './SearchResultsTable';
import CreatedItemsTable from './CreatedItemsTable';

type Props = {
filterArgs?: Record<string, any>;
searchQuery?: Record<string, any>;
@@ -133,9 +134,22 @@ const NewCreateItem: React.FC<Props> = ({ filterArgs, searchQuery, onPickOrderCr
useEffect(() => {
const loadItems = async () => {
try {
const itemsData = await fetchAllItemsInClient();
// Change from fetchAllItemsInClient to fetchItemsWithDetails
const itemsData = await fetchItemsWithDetails();
console.log("Loaded items:", itemsData);
setItems(itemsData);
// Transform the data to match the expected ItemCombo format
// Fix: Access the records property correctly
const transformedItems: ItemCombo[] = (itemsData as any).records?.map((item: any) => ({
id: item.id,
label: `${item.code} - ${item.name}`,
uomId: item.uomId,
uom: item.uom,
uomDesc: item.uomDesc,
currentStockBalance: item.currentStockBalance
})) || [];
setItems(transformedItems);
setFilteredItems([]);
} catch (error) {
console.error("Error loading items:", error);
@@ -159,9 +173,10 @@ const NewCreateItem: React.FC<Props> = ({ filterArgs, searchQuery, onPickOrderCr
qty: item.reqQty,
uom: item.uom,
uomId: 0,
uomDesc: item.uomDesc, // Add missing uomDesc
uomDesc: item.uomDesc || "", // Add missing uomDesc
jobOrderCode: jobOrderDetail.code,
jobOrderId: jobOrderDetail.id,
currentStockBalance: 0, // Add default value
}));
setFilteredItems(convertedItems);
@@ -221,46 +236,8 @@ const NewCreateItem: React.FC<Props> = ({ filterArgs, searchQuery, onPickOrderCr
[formProps],
);

const handleSearch = useCallback(() => {

if (!searchCode && !searchName) {
alert(t("Please enter at least code or name"));
return;
}
setIsLoading(true);
setHasSearched(true);
console.log("Searching with:", { type, searchCode, searchName, targetDate, itemsCount: items.length });
setTimeout(() => {
let filtered = items;
if (searchCode && searchCode.trim()) {
filtered = filtered.filter(item =>
item.label.toLowerCase().includes(searchCode.toLowerCase())
);
console.log("After code filter:", filtered.length);
}
if (searchName && searchName.trim()) {
filtered = filtered.filter(item =>
item.label.toLowerCase().includes(searchName.toLowerCase())
);
console.log("After name filter:", filtered.length);
}
// Convert to SearchItemWithQty with default qty = null and include targetDate
const filteredWithQty = filtered.slice(0, 100).map(item => ({
...item,
qty: null,
targetDate: targetDate, // Add target date to each item
}));
console.log("Final filtered results:", filteredWithQty.length);
setFilteredItems(filteredWithQty);
setIsLoading(false);
}, 500);
}, [type, searchCode, searchName, targetDate, items, t]); // Add targetDate back to dependencies
// Remove the duplicate handleSearch function and keep only this one

// Handle quantity change in search results
const handleSearchQtyChange = useCallback((itemId: number, newQty: number | null) => {
@@ -508,33 +485,78 @@ const handleQtyBlur = useCallback((itemId: number) => {
}, 0);
}, [groups, checkAndAutoAddItem]);
// 5) 选中新增的待选项:依然按“当前 Group”赋 groupId + targetDate(新加入的应随 Group)
const handleSecondSearchItemSelect = useCallback((itemId: number, isSelected: boolean) => {
if (!isSelected) return;
const item = secondSearchResults.find(i => i.id === itemId);
if (!item) return;
const exists = createdItems.find(c => c.itemId === item.id);
if (exists) { alert(t("Item already exists in created items")); return; }

// 找到项目所属的组,使用该组的 targetDate
const itemGroup = groups.find(g => g.id === item.groupId);
const itemTargetDate = itemGroup?.targetDate || item.targetDate || targetDate;

const newCreatedItem: CreatedItem = {
itemId: item.id,
itemName: item.label,
itemCode: item.label,
qty: item.qty || 1,
uom: item.uom || "",
uomId: item.uomId || 0,
uomDesc: item.uomDesc || "",
isSelected: true,
currentStockBalance: item.currentStockBalance,
targetDate: itemTargetDate, // 使用项目所属组的 targetDate
groupId: item.groupId || undefined, // 使用项目自身的 groupId
};
setCreatedItems(prev => [...prev, newCreatedItem]);
}, [secondSearchResults, createdItems, groups, targetDate, t]);

const handleSearch = useCallback(() => {
if (!searchCode && !searchName) {
alert(t("Please enter at least code or name"));
return;
}
setIsLoading(true);
setHasSearched(true);
console.log("Searching with:", { type, searchCode, searchName, targetDate });
// Use the new API with search parameters
const searchParams: Record<string, any> = {};
if (searchCode && searchCode.trim()) {
searchParams.code = searchCode.trim();
}
if (searchName && searchName.trim()) {
searchParams.name = searchName.trim();
}
if (type && type.trim()) {
searchParams.type = type.trim();
}
// Add pagination parameters
searchParams.pageSize = 100;
searchParams.pageNum = 1;
// Fetch items using the new API
fetchItemsWithDetails(searchParams)
.then(response => {
try {
// Fix: Handle the response type correctly and safely
let itemsToTransform: any[] = [];
// Safely check and extract data from response
if (response && typeof response === 'object') {
if ('records' in response && Array.isArray((response as any).records)) {
itemsToTransform = (response as any).records;
} else if (Array.isArray(response)) {
itemsToTransform = response;
}
}
const transformedItems: SearchItemWithQty[] = itemsToTransform.map((item: any) => ({
id: item.id,
label: `${item.code} - ${item.name}`,
uomId: item.uomId,
uom: item.uom,
uomDesc: item.uomDesc,
currentStockBalance: item.currentStockBalance,
qty: null,
targetDate: targetDate,
}));
console.log("Search results:", transformedItems.length);
setFilteredItems(transformedItems);
} catch (error) {
console.error("Error processing response:", error);
setFilteredItems([]);
} finally {
setIsLoading(false);
}
})
.catch(error => {
console.error("Error searching items:", error);
setFilteredItems([]);
setIsLoading(false);
});
}, [type, searchCode, searchName, targetDate, t]);
// 修改提交函数,按组分别创建提料单
const onSubmit = useCallback<SubmitHandler<SearchFormData>>(
async (data, event) => {
@@ -1052,8 +1074,6 @@ const handleQtyBlur = useCallback((itemId: number) => {
// 修改搜索条件为3行,每行一个 - 确保SearchBox组件能正确处理
const pickOrderSearchCriteria: Criterion<any>[] = useMemo(
() => [


{
label: t("Item Code"),
paramName: "code",
@@ -1258,7 +1278,6 @@ const handleQtyBlur = useCallback((itemId: number) => {
// Sync second search box info to form - ensure type value is correct
if (query.type) {
// Ensure type value matches backend enum format
let correctType = query.type;
if (query.type === "consumable") {
correctType = "Consumable";
@@ -1270,39 +1289,68 @@ const handleQtyBlur = useCallback((itemId: number) => {
formProps.setValue("type", correctType);
}
setTimeout(() => {
let filtered = items;
// Same filtering logic as first search
if (query.code && query.code.trim()) {
filtered = filtered.filter(item =>
item.label.toLowerCase().includes(query.code.toLowerCase())
);
}
if (query.name && query.name.trim()) {
filtered = filtered.filter(item =>
item.label.toLowerCase().includes(query.name.toLowerCase())
);
}
if (query.type && query.type !== "All") {
// Filter by type if needed
// Build search parameters for the new API
const searchParams: Record<string, any> = {};
if (query.code && query.code.trim()) {
searchParams.code = query.code.trim();
}
if (query.name && query.name.trim()) {
searchParams.name = query.name.trim();
}
if (query.type && query.type !== "All") {
searchParams.type = query.type;
}
// Add pagination parameters
searchParams.pageSize = 100;
searchParams.pageNum = 1;
// Use the new API
fetchItemsWithDetails(searchParams)
.then(response => {
try {
// Fix: Handle the response type correctly and safely
let itemsToTransform: any[] = [];
// Safely check and extract data from response
if (response && typeof response === 'object') {
if ('records' in response && Array.isArray((response as any).records)) {
itemsToTransform = (response as any).records;
} else if (Array.isArray(response)) {
itemsToTransform = response;
}
}
const transformedItems: SearchItemWithQty[] = itemsToTransform.map((item: any) => ({
id: item.id,
label: `${item.code} - ${item.name}`,
uomId: item.uomId,
uom: item.uom,
uomDesc: item.uomDesc,
currentStockBalance: item.currentStockBalance,
qty: null,
targetDate: undefined,
groupId: undefined,
}));
setSecondSearchResults(transformedItems);
setHasSearchedSecond(true);
} catch (error) {
console.error("Error processing response:", error);
setSecondSearchResults([]);
} finally {
setIsLoadingSecondSearch(false);
}
// Convert to SearchItemWithQty with NO group/targetDate initially
const filteredWithQty = filtered.slice(0, 100).map(item => ({
...item,
qty: null,
targetDate: undefined, // No target date initially
groupId: undefined, // No group initially
}));
setSecondSearchResults(filteredWithQty);
setHasSearchedSecond(true);
})
.catch(error => {
console.error("Error in second search:", error);
setSecondSearchResults([]);
setIsLoadingSecondSearch(false);
}, 500);
}, [items, formProps]);
});
}, [formProps, t]);
/*
// Create a custom search box component that displays fields vertically
const VerticalSearchBox = ({ criteria, onSearch, onReset }: {


正在加载...
取消
保存