FPSMS-frontend
Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 

2060 righe
71 KiB

  1. "use client";
  2. import { createPickOrder, SavePickOrderRequest, SavePickOrderLineRequest, getLatestGroupNameAndCreate, createOrUpdateGroups } from "@/app/api/pickOrder/actions";
  3. import {
  4. Autocomplete,
  5. Box,
  6. Button,
  7. FormControl,
  8. Grid,
  9. Stack,
  10. TextField,
  11. Typography,
  12. Checkbox,
  13. Table,
  14. TableBody,
  15. TableCell,
  16. TableContainer,
  17. TableHead,
  18. TableRow,
  19. Paper,
  20. Select,
  21. MenuItem,
  22. Modal,
  23. Card,
  24. CardContent,
  25. TablePagination,
  26. } from "@mui/material";
  27. import { Controller, FormProvider, SubmitHandler, useForm } from "react-hook-form";
  28. import { useTranslation } from "react-i18next";
  29. import { useCallback, useEffect, useMemo, useState } from "react";
  30. import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers";
  31. import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
  32. import dayjs from "dayjs";
  33. import { Check, Search, RestartAlt } from "@mui/icons-material";
  34. import { ItemCombo, fetchAllItemsInClient,fetchItemsWithDetails } from "@/app/api/settings/item/actions";
  35. import { INPUT_DATE_FORMAT, OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil";
  36. import SearchResults, { Column } from "../SearchResults/SearchResults";
  37. import { fetchJobOrderDetailByCode } from "@/app/api/jo/actions";
  38. import SearchBox, { Criterion } from "../SearchBox";
  39. import VerticalSearchBox from "./VerticalSearchBox";
  40. import SearchResultsTable from './SearchResultsTable';
  41. import CreatedItemsTable from './CreatedItemsTable';
  42. type Props = {
  43. filterArgs?: Record<string, any>;
  44. searchQuery?: Record<string, any>;
  45. onPickOrderCreated?: () => void; // 添加回调函数
  46. };
  47. // 扩展表单类型以包含搜索字段
  48. interface SearchFormData extends SavePickOrderRequest {
  49. searchCode?: string;
  50. searchName?: string;
  51. }
  52. // Update the CreatedItem interface to allow null values for groupId
  53. interface CreatedItem {
  54. itemId: number;
  55. itemName: string;
  56. itemCode: string;
  57. qty: number;
  58. uom: string;
  59. uomId: number;
  60. uomDesc: string;
  61. isSelected: boolean;
  62. currentStockBalance?: number;
  63. targetDate?: string | null; // Make it optional to match the source
  64. groupId?: number | null; // Allow null values
  65. }
  66. // Add interface for search items with quantity
  67. interface SearchItemWithQty extends ItemCombo {
  68. qty: number | null; // Changed from number to number | null
  69. jobOrderCode?: string;
  70. jobOrderId?: number;
  71. currentStockBalance?: number;
  72. targetDate?: string | null; // Allow null values
  73. groupId?: number | null; // Allow null values
  74. }
  75. interface JobOrderDetailPickLine {
  76. id: number;
  77. code: string;
  78. name: string;
  79. lotNo: string | null;
  80. reqQty: number;
  81. uom: string;
  82. status: string;
  83. }
  84. // 添加组相关的接口
  85. interface Group {
  86. id: number;
  87. name: string;
  88. targetDate: string ;
  89. }
  90. // Move the counter outside the component to persist across re-renders
  91. let checkboxChangeCallCount = 0;
  92. let processingItems = new Set<number>();
  93. const NewCreateItem: React.FC<Props> = ({ filterArgs, searchQuery, onPickOrderCreated }) => {
  94. const { t } = useTranslation("pickOrder");
  95. const [items, setItems] = useState<ItemCombo[]>([]);
  96. const [filteredItems, setFilteredItems] = useState<SearchItemWithQty[]>([]);
  97. const [createdItems, setCreatedItems] = useState<CreatedItem[]>([]);
  98. const [isLoading, setIsLoading] = useState(false);
  99. const [hasSearched, setHasSearched] = useState(false);
  100. // 添加组相关的状态 - 只声明一次
  101. const [groups, setGroups] = useState<Group[]>([]);
  102. const [selectedGroup, setSelectedGroup] = useState<Group | null>(null);
  103. const [nextGroupNumber, setNextGroupNumber] = useState(1);
  104. // Add state for selected item IDs in search results
  105. const [selectedSearchItemIds, setSelectedSearchItemIds] = useState<(string | number)[]>([]);
  106. // Add state for second search
  107. const [secondSearchQuery, setSecondSearchQuery] = useState<Record<string, any>>({});
  108. const [secondSearchResults, setSecondSearchResults] = useState<SearchItemWithQty[]>([]);
  109. const [isLoadingSecondSearch, setIsLoadingSecondSearch] = useState(false);
  110. const [hasSearchedSecond, setHasSearchedSecond] = useState(false);
  111. // Add selection state for second search
  112. const [selectedSecondSearchItemIds, setSelectedSecondSearchItemIds] = useState<(string | number)[]>([]);
  113. const formProps = useForm<SearchFormData>();
  114. const errors = formProps.formState.errors;
  115. const targetDate = formProps.watch("targetDate");
  116. const type = formProps.watch("type");
  117. const searchCode = formProps.watch("searchCode");
  118. const searchName = formProps.watch("searchName");
  119. const [jobOrderItems, setJobOrderItems] = useState<JobOrderDetailPickLine[]>([]);
  120. const [isLoadingJobOrder, setIsLoadingJobOrder] = useState(false);
  121. useEffect(() => {
  122. const loadItems = async () => {
  123. try {
  124. // Change from fetchAllItemsInClient to fetchItemsWithDetails
  125. const itemsData = await fetchItemsWithDetails();
  126. console.log("Loaded items:", itemsData);
  127. // Transform the data to match the expected ItemCombo format
  128. // Fix: Access the records property correctly
  129. const transformedItems: ItemCombo[] = (itemsData as any).records?.map((item: any) => ({
  130. id: item.id,
  131. label: `${item.code} - ${item.name}`,
  132. uomId: item.uomId,
  133. uom: item.uom,
  134. uomDesc: item.uomDesc,
  135. currentStockBalance: item.currentStockBalance
  136. })) || [];
  137. setItems(transformedItems);
  138. setFilteredItems([]);
  139. } catch (error) {
  140. console.error("Error loading items:", error);
  141. }
  142. };
  143. loadItems();
  144. }, []);
  145. const searchJobOrderItems = useCallback(async (jobOrderCode: string) => {
  146. if (!jobOrderCode.trim()) return;
  147. setIsLoadingJobOrder(true);
  148. try {
  149. const jobOrderDetail = await fetchJobOrderDetailByCode(jobOrderCode);
  150. setJobOrderItems(jobOrderDetail.pickLines || []);
  151. // Fix the Job Order conversion - add missing uomDesc
  152. const convertedItems = (jobOrderDetail.pickLines || []).map(item => ({
  153. id: item.id,
  154. label: item.name,
  155. qty: item.reqQty,
  156. uom: item.uom,
  157. uomId: 0,
  158. uomDesc: item.uomDesc || "", // Add missing uomDesc
  159. jobOrderCode: jobOrderDetail.code,
  160. jobOrderId: jobOrderDetail.id,
  161. currentStockBalance: 0, // Add default value
  162. }));
  163. setFilteredItems(convertedItems);
  164. setHasSearched(true);
  165. } catch (error) {
  166. console.error("Error fetching Job Order items:", error);
  167. alert(t("Job Order not found or has no items"));
  168. } finally {
  169. setIsLoadingJobOrder(false);
  170. }
  171. }, [t]);
  172. // Update useEffect to handle Job Order search
  173. useEffect(() => {
  174. if (searchQuery && searchQuery.jobOrderCode) {
  175. searchJobOrderItems(searchQuery.jobOrderCode);
  176. } else if (searchQuery && items.length > 0) {
  177. // Existing item search logic
  178. // ... your existing search logic
  179. }
  180. }, [searchQuery, items, searchJobOrderItems]);
  181. useEffect(() => {
  182. if (searchQuery) {
  183. if (searchQuery.type) {
  184. formProps.setValue("type", searchQuery.type);
  185. }
  186. if (searchQuery.targetDate) {
  187. formProps.setValue("targetDate", searchQuery.targetDate);
  188. }
  189. if (searchQuery.code) {
  190. formProps.setValue("searchCode", searchQuery.code);
  191. }
  192. if (searchQuery.items) {
  193. formProps.setValue("searchName", searchQuery.items);
  194. }
  195. }
  196. }, [searchQuery, formProps]);
  197. useEffect(() => {
  198. setFilteredItems([]);
  199. setHasSearched(false);
  200. }, []);
  201. const typeList = [
  202. { type: "Consumable" },
  203. { type: "Material" },
  204. { type: "Product" }
  205. ];
  206. const handleTypeChange = useCallback(
  207. (event: React.SyntheticEvent, newValue: {type: string} | null) => {
  208. formProps.setValue("type", newValue?.type || "");
  209. },
  210. [formProps],
  211. );
  212. // Remove the duplicate handleSearch function and keep only this one
  213. // Handle quantity change in search results
  214. const handleSearchQtyChange = useCallback((itemId: number, newQty: number | null) => {
  215. setFilteredItems(prev =>
  216. prev.map(item =>
  217. item.id === itemId ? { ...item, qty: newQty } : item
  218. )
  219. );
  220. // Auto-update created items if this item exists there
  221. setCreatedItems(prev =>
  222. prev.map(item =>
  223. item.itemId === itemId ? { ...item, qty: newQty || 1 } : item
  224. )
  225. );
  226. }, []);
  227. // Modified handler for search item selection
  228. const handleSearchItemSelect = useCallback((itemId: number, isSelected: boolean) => {
  229. if (isSelected) {
  230. const item = filteredItems.find(i => i.id === itemId);
  231. if (!item) return;
  232. const existingItem = createdItems.find(created => created.itemId === item.id);
  233. if (existingItem) {
  234. alert(t("Item already exists in created items"));
  235. return;
  236. }
  237. // Fix the newCreatedItem creation - add missing uomDesc
  238. const newCreatedItem: CreatedItem = {
  239. itemId: item.id,
  240. itemName: item.label,
  241. itemCode: item.label,
  242. qty: item.qty || 1,
  243. uom: item.uom || "",
  244. uomId: item.uomId || 0,
  245. uomDesc: item.uomDesc || "", // Add missing uomDesc
  246. isSelected: true,
  247. currentStockBalance: item.currentStockBalance,
  248. targetDate: item.targetDate || targetDate, // Use item's targetDate or fallback to form's targetDate
  249. groupId: item.groupId || undefined, // Handle null values
  250. };
  251. setCreatedItems(prev => [...prev, newCreatedItem]);
  252. }
  253. }, [filteredItems, createdItems, t, targetDate]);
  254. // Handler for created item selection
  255. const handleCreatedItemSelect = useCallback((itemId: number, isSelected: boolean) => {
  256. setCreatedItems(prev =>
  257. prev.map(item =>
  258. item.itemId === itemId ? { ...item, isSelected } : item
  259. )
  260. );
  261. }, []);
  262. const handleQtyChange = useCallback((itemId: number, newQty: number) => {
  263. setCreatedItems(prev =>
  264. prev.map(item =>
  265. item.itemId === itemId ? { ...item, qty: newQty } : item
  266. )
  267. );
  268. }, []);
  269. // Check if item is already in created items
  270. const isItemInCreated = useCallback((itemId: number) => {
  271. return createdItems.some(item => item.itemId === itemId);
  272. }, [createdItems]);
  273. // 1) Created Items 行内改组:只改这一行的 groupId,并把该行 targetDate 同步为该组日期
  274. const handleCreatedItemGroupChange = useCallback((itemId: number, newGroupId: string) => {
  275. const gid = newGroupId ? Number(newGroupId) : undefined;
  276. const group = groups.find(g => g.id === gid);
  277. setCreatedItems(prev =>
  278. prev.map(it =>
  279. it.itemId === itemId
  280. ? {
  281. ...it,
  282. groupId: gid,
  283. targetDate: group?.targetDate || it.targetDate,
  284. }
  285. : it,
  286. ),
  287. );
  288. }, [groups]);
  289. // Update the handleGroupChange function to update target dates for items in the selected group
  290. const handleGroupChange = useCallback((groupId: string | number) => {
  291. const gid = typeof groupId === "string" ? Number(groupId) : groupId;
  292. const group = groups.find(g => g.id === gid);
  293. if (!group) return;
  294. setSelectedGroup(group);
  295. // Update target dates for items that belong to this group
  296. setSecondSearchResults(prev => prev.map(item =>
  297. item.groupId === gid
  298. ? {
  299. ...item,
  300. targetDate: group.targetDate
  301. }
  302. : item
  303. ));
  304. }, [groups]);
  305. // Update the handleGroupTargetDateChange function to update selected items that belong to that group
  306. const handleGroupTargetDateChange = useCallback((groupId: number, newTargetDate: string) => {
  307. setGroups(prev => prev.map(g => (g.id === groupId ? { ...g, targetDate: newTargetDate } : g)));
  308. setSelectedGroup(prev => {
  309. if (prev && prev.id === groupId) {
  310. return { ...prev, targetDate: newTargetDate };
  311. }
  312. return prev;
  313. });
  314. // Update selected items that belong to this group
  315. setSecondSearchResults(prev => prev.map(item =>
  316. item.groupId === groupId
  317. ? {
  318. ...item,
  319. targetDate: newTargetDate
  320. }
  321. : item
  322. ));
  323. setCreatedItems(prev => prev.map(item =>
  324. item.groupId === groupId
  325. ? {
  326. ...item,
  327. targetDate: newTargetDate
  328. }
  329. : item
  330. ));
  331. }, []);
  332. // Fix the handleCreateGroup function to use the API properly
  333. const handleCreateGroup = useCallback(async () => {
  334. try {
  335. // Use the API to get latest group name and create it automatically
  336. const response = await getLatestGroupNameAndCreate();
  337. if (response.id && response.name) {
  338. const newGroup: Group = {
  339. id: response.id,
  340. name: response.name,
  341. targetDate: ""
  342. };
  343. setGroups(prev => [...prev, newGroup]);
  344. setSelectedGroup(newGroup);
  345. console.log(`Created new group: ${response.name}`);
  346. } else {
  347. alert(t('Failed to create group'));
  348. }
  349. } catch (error) {
  350. console.error('Error creating group:', error);
  351. alert(t('Failed to create group'));
  352. }
  353. }, [t]);
  354. const checkAndAutoAddItem = useCallback((itemId: number) => {
  355. const item = secondSearchResults.find(i => i.id === itemId);
  356. if (!item) return;
  357. // Check if item has ALL 3 conditions:
  358. // 1. Item is selected (checkbox checked)
  359. const isSelected = selectedSecondSearchItemIds.includes(itemId);
  360. // 2. Group is assigned
  361. const hasGroup = item.groupId !== undefined && item.groupId !== null;
  362. // 3. Quantity is entered
  363. const hasQty = item.qty !== null && item.qty !== undefined && item.qty > 0;
  364. if (isSelected && hasGroup && hasQty && !isItemInCreated(item.id)) {
  365. // Auto-add to created items
  366. const newCreatedItem: CreatedItem = {
  367. itemId: item.id,
  368. itemName: item.label,
  369. itemCode: item.label,
  370. qty: item.qty || 1,
  371. uom: item.uom || "",
  372. uomId: item.uomId || 0,
  373. uomDesc: item.uomDesc || "",
  374. isSelected: true,
  375. currentStockBalance: item.currentStockBalance,
  376. targetDate: item.targetDate || targetDate,
  377. groupId: item.groupId || undefined,
  378. };
  379. setCreatedItems(prev => [...prev, newCreatedItem]);
  380. // Remove from search results since it's now in created items
  381. setSecondSearchResults(prev => prev.filter(searchItem => searchItem.id !== itemId));
  382. setSelectedSecondSearchItemIds(prev => prev.filter(id => id !== itemId));
  383. }
  384. }, [secondSearchResults, selectedSecondSearchItemIds, isItemInCreated, targetDate]);
  385. // Add this function after checkAndAutoAddItem
  386. // Add this function after checkAndAutoAddItem
  387. const handleQtyBlur = useCallback((itemId: number) => {
  388. // Only auto-add if item is already selected (scenario 1: select first, then enter quantity)
  389. setTimeout(() => {
  390. const currentItem = secondSearchResults.find(i => i.id === itemId);
  391. if (!currentItem) return;
  392. const isSelected = selectedSecondSearchItemIds.includes(itemId);
  393. const hasGroup = currentItem.groupId !== undefined && currentItem.groupId !== null;
  394. const hasQty = currentItem.qty !== null && currentItem.qty !== undefined && currentItem.qty > 0;
  395. // Only auto-add if item is already selected (scenario 1: select first, then enter quantity)
  396. if (isSelected && hasGroup && hasQty && !isItemInCreated(currentItem.id)) {
  397. const newCreatedItem: CreatedItem = {
  398. itemId: currentItem.id,
  399. itemName: currentItem.label,
  400. itemCode: currentItem.label,
  401. qty: currentItem.qty || 1,
  402. uom: currentItem.uom || "",
  403. uomId: currentItem.uomId || 0,
  404. uomDesc: currentItem.uomDesc || "",
  405. isSelected: true,
  406. currentStockBalance: currentItem.currentStockBalance,
  407. targetDate: currentItem.targetDate || targetDate,
  408. groupId: currentItem.groupId || undefined,
  409. };
  410. setCreatedItems(prev => [...prev, newCreatedItem]);
  411. setSecondSearchResults(prev => prev.filter(searchItem => searchItem.id !== itemId));
  412. setSelectedSecondSearchItemIds(prev => prev.filter(id => id !== itemId));
  413. }
  414. }, 0);
  415. }, [secondSearchResults, selectedSecondSearchItemIds, isItemInCreated, targetDate]);
  416. const handleSearchItemGroupChange = useCallback((itemId: number, groupId: string) => {
  417. const gid = groupId ? Number(groupId) : undefined;
  418. const group = groups.find(g => g.id === gid);
  419. setSecondSearchResults(prev => prev.map(item =>
  420. item.id === itemId
  421. ? {
  422. ...item,
  423. groupId: gid,
  424. targetDate: group?.targetDate || undefined
  425. }
  426. : item
  427. ));
  428. // Check auto-add after group assignment
  429. setTimeout(() => {
  430. checkAndAutoAddItem(itemId);
  431. }, 0);
  432. }, [groups, checkAndAutoAddItem]);
  433. // 5) 选中新增的待选项:依然按“当前 Group”赋 groupId + targetDate(新加入的应随 Group)
  434. const handleSearch = useCallback(() => {
  435. if (!searchCode && !searchName) {
  436. alert(t("Please enter at least code or name"));
  437. return;
  438. }
  439. setIsLoading(true);
  440. setHasSearched(true);
  441. console.log("Searching with:", { type, searchCode, searchName, targetDate });
  442. // Use the new API with search parameters
  443. const searchParams: Record<string, any> = {};
  444. if (searchCode && searchCode.trim()) {
  445. searchParams.code = searchCode.trim();
  446. }
  447. if (searchName && searchName.trim()) {
  448. searchParams.name = searchName.trim();
  449. }
  450. if (type && type.trim()) {
  451. searchParams.type = type.trim();
  452. }
  453. // Add pagination parameters
  454. searchParams.pageSize = 100;
  455. searchParams.pageNum = 1;
  456. // Fetch items using the new API
  457. fetchItemsWithDetails(searchParams)
  458. .then(response => {
  459. try {
  460. // Fix: Handle the response type correctly and safely
  461. let itemsToTransform: any[] = [];
  462. // Safely check and extract data from response
  463. if (response && typeof response === 'object') {
  464. if ('records' in response && Array.isArray((response as any).records)) {
  465. itemsToTransform = (response as any).records;
  466. } else if (Array.isArray(response)) {
  467. itemsToTransform = response;
  468. }
  469. }
  470. const transformedItems: SearchItemWithQty[] = itemsToTransform.map((item: any) => ({
  471. id: item.id,
  472. label: `${item.code} - ${item.name}`,
  473. uomId: item.uomId,
  474. uom: item.uom,
  475. uomDesc: item.uomDesc,
  476. currentStockBalance: item.currentStockBalance,
  477. qty: null,
  478. targetDate: targetDate,
  479. }));
  480. console.log("Search results:", transformedItems.length);
  481. setFilteredItems(transformedItems);
  482. } catch (error) {
  483. console.error("Error processing response:", error);
  484. setFilteredItems([]);
  485. } finally {
  486. setIsLoading(false);
  487. }
  488. })
  489. .catch(error => {
  490. console.error("Error searching items:", error);
  491. setFilteredItems([]);
  492. setIsLoading(false);
  493. });
  494. }, [type, searchCode, searchName, targetDate, t]);
  495. // 修改提交函数,按组分别创建提料单
  496. const onSubmit = useCallback<SubmitHandler<SearchFormData>>(
  497. async (data, event) => {
  498. const selectedCreatedItems = createdItems.filter(item => item.isSelected);
  499. if (selectedCreatedItems.length === 0) {
  500. alert(t("Please select at least one item to submit"));
  501. return;
  502. }
  503. // 修复:自动填充 type 为 "Consumable",不再强制用户选择
  504. // if (!data.type) {
  505. // alert(t("Please select product type"));
  506. // return;
  507. // }
  508. // 按组分组选中的项目
  509. const itemsByGroup = selectedCreatedItems.reduce((acc, item) => {
  510. const groupId = item.groupId || 'no-group';
  511. if (!acc[groupId]) {
  512. acc[groupId] = [];
  513. }
  514. acc[groupId].push(item);
  515. return acc;
  516. }, {} as Record<string | number, typeof selectedCreatedItems>);
  517. console.log("Items grouped by group:", itemsByGroup);
  518. let successCount = 0;
  519. const totalGroups = Object.keys(itemsByGroup).length;
  520. const groupUpdates: Array<{groupId: number, pickOrderId: number}> = [];
  521. // 为每个组创建提料单
  522. for (const [groupId, items] of Object.entries(itemsByGroup)) {
  523. try {
  524. // 获取组的名称和目标日期
  525. const group = groups.find(g => g.id === Number(groupId));
  526. const groupName = group?.name || t('No Group');
  527. // Use the group's target date, fallback to item's target date, then form's target date
  528. let groupTargetDate = group?.targetDate;
  529. if (!groupTargetDate && items.length > 0) {
  530. groupTargetDate = items[0].targetDate || undefined; // Add || undefined to handle null
  531. }
  532. if (!groupTargetDate) {
  533. groupTargetDate = data.targetDate;
  534. }
  535. // If still no target date, use today
  536. if (!groupTargetDate) {
  537. groupTargetDate = dayjs().format(INPUT_DATE_FORMAT);
  538. }
  539. console.log(`Creating pick order for group: ${groupName} with ${items.length} items, target date: ${groupTargetDate}`);
  540. let formattedTargetDate = groupTargetDate;
  541. if (groupTargetDate && typeof groupTargetDate === 'string') {
  542. try {
  543. const date = dayjs(groupTargetDate);
  544. formattedTargetDate = date.format('YYYY-MM-DD');
  545. } catch (error) {
  546. console.error("Invalid date format:", groupTargetDate);
  547. alert(t("Invalid date format"));
  548. return;
  549. }
  550. }
  551. // 修复:自动使用 "Consumable" 作为默认 type
  552. const pickOrderData: SavePickOrderRequest = {
  553. type: data.type || "Consumable", // 如果用户选择了 type 就用用户的,否则默认 "Consumable"
  554. targetDate: formattedTargetDate,
  555. pickOrderLine: items.map(item => ({
  556. itemId: item.itemId,
  557. qty: item.qty,
  558. uomId: item.uomId
  559. } as SavePickOrderLineRequest))
  560. };
  561. console.log(`Submitting pick order for group ${groupName}:`, pickOrderData);
  562. const res = await createPickOrder(pickOrderData);
  563. if (res.id) {
  564. console.log(`Pick order created successfully for group ${groupName}:`, res);
  565. successCount++;
  566. // Store group ID and pick order ID for updating
  567. if (groupId !== 'no-group' && group?.id) {
  568. groupUpdates.push({
  569. groupId: group.id,
  570. pickOrderId: res.id
  571. });
  572. }
  573. } else {
  574. console.error(`Failed to create pick order for group ${groupName}:`, res);
  575. alert(t(`Failed to create pick order for group ${groupName}`));
  576. return;
  577. }
  578. } catch (error) {
  579. console.error(`Error creating pick order for group ${groupId}:`, error);
  580. alert(t(`Error creating pick order for group ${groupId}`));
  581. return;
  582. }
  583. }
  584. // Update groups with pick order information
  585. if (groupUpdates.length > 0) {
  586. try {
  587. // Update each group with its corresponding pick order ID
  588. for (const update of groupUpdates) {
  589. const updateResponse = await createOrUpdateGroups({
  590. groupIds: [update.groupId],
  591. targetDate: data.targetDate,
  592. pickOrderId: update.pickOrderId
  593. });
  594. console.log(`Group ${update.groupId} updated with pick order ${update.pickOrderId}:`, updateResponse);
  595. }
  596. } catch (error) {
  597. console.error('Error updating groups:', error);
  598. // Don't fail the whole operation if group update fails
  599. }
  600. }
  601. // 所有组都创建成功后,清理选中的项目并切换到 Assign & Release
  602. if (successCount === totalGroups) {
  603. setCreatedItems(prev => prev.filter(item => !item.isSelected));
  604. formProps.reset();
  605. setHasSearched(false);
  606. setFilteredItems([]);
  607. // alert(t("All pick orders created successfully"));
  608. // 通知父组件切换到 Assign & Release 标签页
  609. if (onPickOrderCreated) {
  610. onPickOrderCreated();
  611. }
  612. }
  613. },
  614. [createdItems, t, formProps, groups, onPickOrderCreated]
  615. );
  616. // Fix the handleReset function to properly clear all states including search results
  617. const handleReset = useCallback(() => {
  618. formProps.reset();
  619. setCreatedItems([]);
  620. setHasSearched(false);
  621. setFilteredItems([]);
  622. // Clear second search states completely
  623. setSecondSearchResults([]);
  624. setHasSearchedSecond(false);
  625. setSelectedSecondSearchItemIds([]);
  626. setSecondSearchQuery({});
  627. // Clear groups
  628. setGroups([]);
  629. setSelectedGroup(null);
  630. setNextGroupNumber(1);
  631. // Clear pagination states
  632. setSearchResultsPagingController({
  633. pageNum: 1,
  634. pageSize: 10,
  635. });
  636. setCreatedItemsPagingController({
  637. pageNum: 1,
  638. pageSize: 10,
  639. });
  640. // Clear first search states
  641. setSelectedSearchItemIds([]);
  642. }, [formProps]);
  643. // Pagination state
  644. const [page, setPage] = useState(0);
  645. const [rowsPerPage, setRowsPerPage] = useState(10);
  646. // Handle page change
  647. const handleChangePage = (
  648. _event: React.MouseEvent | React.KeyboardEvent,
  649. newPage: number,
  650. ) => {
  651. console.log(_event);
  652. setPage(newPage);
  653. // The original code had setPagingController and defaultPagingController,
  654. // but these are not defined in the provided context.
  655. // Assuming they are meant to be part of a larger context or will be added.
  656. // For now, commenting out the setPagingController part as it's not defined.
  657. // if (setPagingController) {
  658. // setPagingController({
  659. // ...(pagingController ?? defaultPagingController),
  660. // pageNum: newPage + 1,
  661. // });
  662. // }
  663. };
  664. // Handle rows per page change
  665. const handleChangeRowsPerPage = (
  666. event: React.ChangeEvent<HTMLInputElement>,
  667. ) => {
  668. console.log(event);
  669. setRowsPerPage(+event.target.value);
  670. setPage(0);
  671. // The original code had setPagingController and defaultPagingController,
  672. // but these are not defined in the provided context.
  673. // Assuming they are meant to be part of a larger context or will be added.
  674. // For now, commenting out the setPagingController part as it's not defined.
  675. // if (setPagingController) {
  676. // setPagingController({
  677. // ...(pagingController ?? defaultPagingController),
  678. // pageNum: 1,
  679. // });
  680. // }
  681. };
  682. // Add missing handleSearchCheckboxChange function
  683. const handleSearchCheckboxChange = useCallback((ids: (string | number)[] | ((prev: (string | number)[]) => (string | number)[])) => {
  684. if (typeof ids === 'function') {
  685. const newIds = ids(selectedSearchItemIds);
  686. setSelectedSearchItemIds(newIds);
  687. if (newIds.length === filteredItems.length) {
  688. // Select all
  689. filteredItems.forEach(item => {
  690. if (!isItemInCreated(item.id)) {
  691. handleSearchItemSelect(item.id, true);
  692. }
  693. });
  694. } else {
  695. // Handle individual selections
  696. filteredItems.forEach(item => {
  697. const isSelected = newIds.includes(item.id);
  698. const isCurrentlyInCreated = isItemInCreated(item.id);
  699. if (isSelected && !isCurrentlyInCreated) {
  700. handleSearchItemSelect(item.id, true);
  701. } else if (!isSelected && isCurrentlyInCreated) {
  702. setCreatedItems(prev => prev.filter(createdItem => createdItem.itemId !== item.id));
  703. }
  704. });
  705. }
  706. } else {
  707. const previousIds = selectedSearchItemIds;
  708. setSelectedSearchItemIds(ids);
  709. const newlySelected = ids.filter(id => !previousIds.includes(id));
  710. const newlyDeselected = previousIds.filter(id => !ids.includes(id));
  711. newlySelected.forEach(id => {
  712. if (!isItemInCreated(id as number)) {
  713. handleSearchItemSelect(id as number, true);
  714. }
  715. });
  716. newlyDeselected.forEach(id => {
  717. setCreatedItems(prev => prev.filter(createdItem => createdItem.itemId !== id));
  718. });
  719. }
  720. }, [selectedSearchItemIds, filteredItems, isItemInCreated, handleSearchItemSelect]);
  721. // Add pagination state for created items
  722. const [createdItemsPagingController, setCreatedItemsPagingController] = useState({
  723. pageNum: 1,
  724. pageSize: 10,
  725. });
  726. // Add pagination handlers for created items
  727. const handleCreatedItemsPageChange = useCallback((event: unknown, newPage: number) => {
  728. const newPagingController = {
  729. ...createdItemsPagingController,
  730. pageNum: newPage + 1,
  731. };
  732. setCreatedItemsPagingController(newPagingController);
  733. }, [createdItemsPagingController]);
  734. const handleCreatedItemsPageSizeChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
  735. const newPageSize = parseInt(event.target.value, 10);
  736. const newPagingController = {
  737. pageNum: 1,
  738. pageSize: newPageSize,
  739. };
  740. setCreatedItemsPagingController(newPagingController);
  741. }, []);
  742. // Create a custom table for created items with pagination
  743. const CustomCreatedItemsTable = () => {
  744. const startIndex = (createdItemsPagingController.pageNum - 1) * createdItemsPagingController.pageSize;
  745. const endIndex = startIndex + createdItemsPagingController.pageSize;
  746. const paginatedCreatedItems = createdItems.slice(startIndex, endIndex);
  747. return (
  748. <>
  749. <TableContainer component={Paper}>
  750. <Table>
  751. <TableHead>
  752. <TableRow>
  753. <TableCell padding="checkbox" sx={{ width: '80px', minWidth: '80px' }}>
  754. {t("Selected")}
  755. </TableCell>
  756. <TableCell>
  757. {t("Item")}
  758. </TableCell>
  759. <TableCell>
  760. {t("Group")}
  761. </TableCell>
  762. <TableCell align="right">
  763. {t("Current Stock")}
  764. </TableCell>
  765. <TableCell align="right">
  766. {t("Stock Unit")}
  767. </TableCell>
  768. <TableCell align="right">
  769. {t("Order Quantity")}
  770. </TableCell>
  771. <TableCell align="right">
  772. {t("Target Date")}
  773. </TableCell>
  774. </TableRow>
  775. </TableHead>
  776. <TableBody>
  777. {paginatedCreatedItems.length === 0 ? (
  778. <TableRow>
  779. <TableCell colSpan={12} align="center">
  780. <Typography variant="body2" color="text.secondary">
  781. {t("No created items")}
  782. </Typography>
  783. </TableCell>
  784. </TableRow>
  785. ) : (
  786. paginatedCreatedItems.map((item) => (
  787. <TableRow key={item.itemId}>
  788. <TableCell padding="checkbox">
  789. <Checkbox
  790. checked={item.isSelected}
  791. onChange={(e) => handleCreatedItemSelect(item.itemId, e.target.checked)}
  792. />
  793. </TableCell>
  794. <TableCell>
  795. <Typography variant="body2">{item.itemName}</Typography>
  796. <Typography variant="caption" color="textSecondary">
  797. {item.itemCode}
  798. </Typography>
  799. </TableCell>
  800. <TableCell>
  801. <FormControl size="small" sx={{ minWidth: 120 }}>
  802. <Select
  803. value={item.groupId?.toString() || ""}
  804. onChange={(e) => handleCreatedItemGroupChange(item.itemId, e.target.value)}
  805. displayEmpty
  806. >
  807. <MenuItem value="">
  808. <em>{t("No Group")}</em>
  809. </MenuItem>
  810. {groups.map((group) => (
  811. <MenuItem key={group.id} value={group.id.toString()}>
  812. {group.name}
  813. if(group.name === t("No Group")){
  814. <em>{t("No Group")}</em>
  815. } else {
  816. group.name
  817. }
  818. </MenuItem>
  819. ))}
  820. </Select>
  821. </FormControl>
  822. </TableCell>
  823. <TableCell align="right">
  824. <Typography
  825. variant="body2"
  826. color={item.currentStockBalance && item.currentStockBalance > 0 ? "success.main" : "error.main"}
  827. >
  828. {item.currentStockBalance?.toLocaleString() || 0}
  829. </Typography>
  830. </TableCell>
  831. <TableCell align="right">
  832. <Typography variant="body2">{item.uomDesc}</Typography>
  833. </TableCell>
  834. <TableCell align="right">
  835. <TextField
  836. type="number"
  837. size="small"
  838. value={item.qty || ""}
  839. onChange={(e) => {
  840. const newQty = Number(e.target.value);
  841. handleQtyChange(item.itemId, newQty);
  842. }}
  843. inputProps={{
  844. min: 1,
  845. step: 1,
  846. style: { textAlign: 'center' }
  847. }}
  848. sx={{
  849. width: '80px',
  850. '& .MuiInputBase-input': {
  851. textAlign: 'center',
  852. cursor: 'text'
  853. }
  854. }}
  855. />
  856. </TableCell>
  857. <TableCell align="right">
  858. <Typography variant="body2">
  859. {item.targetDate&& item.targetDate !== "" ? dayjs(item.targetDate).format(OUTPUT_DATE_FORMAT) : "-"}
  860. </Typography>
  861. </TableCell>
  862. </TableRow>
  863. ))
  864. )}
  865. </TableBody>
  866. </Table>
  867. </TableContainer>
  868. {/* Pagination for created items */}
  869. <TablePagination
  870. component="div"
  871. count={createdItems.length}
  872. page={(createdItemsPagingController.pageNum - 1)}
  873. rowsPerPage={createdItemsPagingController.pageSize}
  874. onPageChange={handleCreatedItemsPageChange}
  875. onRowsPerPageChange={handleCreatedItemsPageSizeChange}
  876. rowsPerPageOptions={[10, 25, 50]}
  877. labelRowsPerPage={t("Rows per page")}
  878. labelDisplayedRows={({ from, to, count }) =>
  879. `${from}-${to} of ${count !== -1 ? count : `more than ${to}`}`
  880. }
  881. />
  882. </>
  883. );
  884. };
  885. // Define columns for SearchResults
  886. const searchItemColumns: Column<SearchItemWithQty>[] = useMemo(() => [
  887. {
  888. name: "id",
  889. label: "",
  890. type: "checkbox",
  891. disabled: (item) => isItemInCreated(item.id), // Disable if already in created items
  892. },
  893. {
  894. name: "label",
  895. label: t("Item"),
  896. renderCell: (item) => {
  897. const parts = item.label.split(' - ');
  898. const code = parts[0] || '';
  899. const name = parts[1] || '';
  900. return (
  901. <Box>
  902. <Typography variant="body2">
  903. {name} {/* 显示项目名称 */}
  904. </Typography>
  905. <Typography variant="caption" color="textSecondary">
  906. {code} {/* 显示项目代码 */}
  907. </Typography>
  908. </Box>
  909. );
  910. },
  911. },
  912. {
  913. name: "qty",
  914. label: t("Order Quantity"),
  915. renderCell: (item) => (
  916. <TextField
  917. type="number"
  918. size="small"
  919. value={item.qty || ""} // Show empty string if qty is null
  920. onChange={(e) => {
  921. const value = e.target.value;
  922. const numValue = value === "" ? null : Number(value);
  923. handleSearchQtyChange(item.id, numValue);
  924. }}
  925. inputProps={{
  926. min: 1,
  927. step: 1,
  928. style: { textAlign: 'center' } // Center the text
  929. }}
  930. sx={{
  931. width: '80px',
  932. '& .MuiInputBase-input': {
  933. textAlign: 'center',
  934. cursor: 'text'
  935. }
  936. }}
  937. />
  938. ),
  939. },
  940. {
  941. name: "currentStockBalance",
  942. label: t("Current Stock"),
  943. renderCell: (item) => {
  944. const stockBalance = item.currentStockBalance || 0;
  945. return (
  946. <Typography
  947. variant="body2"
  948. color={stockBalance > 0 ? "success.main" : "error.main"}
  949. sx={{ fontWeight: stockBalance > 0 ? 'bold' : 'normal' }}
  950. >
  951. {stockBalance?.toLocaleString() }
  952. </Typography>
  953. );
  954. },
  955. },
  956. {
  957. name: "targetDate",
  958. label: t("Target Date"),
  959. renderCell: (item) => (
  960. <Typography variant="body2">
  961. {item.targetDate ? dayjs(item.targetDate).format(OUTPUT_DATE_FORMAT) : "-"}
  962. </Typography>
  963. ),
  964. },
  965. {
  966. name: "uom",
  967. label: t("Stock Unit"),
  968. renderCell: (item) => item.uom || "-",
  969. },
  970. ], [t, isItemInCreated, handleSearchQtyChange]);
  971. // 修改搜索条件为3行,每行一个 - 确保SearchBox组件能正确处理
  972. const pickOrderSearchCriteria: Criterion<any>[] = useMemo(
  973. () => [
  974. {
  975. label: t("Item Code"),
  976. paramName: "code",
  977. type: "text"
  978. },
  979. {
  980. label: t("Item Name"),
  981. paramName: "name",
  982. type: "text"
  983. },
  984. {
  985. label: t("Product Type"),
  986. paramName: "type",
  987. type: "autocomplete",
  988. options: [
  989. { value: "Consumable", label: t("Consumable") },
  990. { value: "MATERIAL", label: t("Material") },
  991. { value: "End_product", label: t("End Product") }
  992. ],
  993. },
  994. ],
  995. [t],
  996. );
  997. // 添加重置函数
  998. const handleSecondReset = useCallback(() => {
  999. console.log("Second search reset");
  1000. setSecondSearchQuery({});
  1001. setSecondSearchResults([]);
  1002. setHasSearchedSecond(false);
  1003. // 清空表单中的类型,但保留今天的日期
  1004. formProps.setValue("type", "");
  1005. const today = dayjs().format(INPUT_DATE_FORMAT);
  1006. formProps.setValue("targetDate", today);
  1007. }, [formProps]);
  1008. // 添加数量变更处理函数
  1009. const handleSecondSearchQtyChange = useCallback((itemId: number, newQty: number | null) => {
  1010. setSecondSearchResults(prev =>
  1011. prev.map(item =>
  1012. item.id === itemId ? { ...item, qty: newQty } : item
  1013. )
  1014. );
  1015. // Don't auto-add here - only on blur event
  1016. }, []);
  1017. // Add checkbox change handler for second search
  1018. const handleSecondSearchCheckboxChange = useCallback((ids: (string | number)[] | ((prev: (string | number)[]) => (string | number)[])) => {
  1019. if (typeof ids === 'function') {
  1020. const newIds = ids(selectedSecondSearchItemIds);
  1021. setSelectedSecondSearchItemIds(newIds);
  1022. // 处理全选逻辑 - 选择所有搜索结果,不仅仅是当前页面
  1023. if (newIds.length === secondSearchResults.length) {
  1024. // 全选:将所有搜索结果添加到创建项目
  1025. secondSearchResults.forEach(item => {
  1026. if (!isItemInCreated(item.id)) {
  1027. handleSearchItemSelect(item.id, true);
  1028. }
  1029. });
  1030. } else {
  1031. // 部分选择:只处理当前页面的选择
  1032. secondSearchResults.forEach(item => {
  1033. const isSelected = newIds.includes(item.id);
  1034. const isCurrentlyInCreated = isItemInCreated(item.id);
  1035. if (isSelected && !isCurrentlyInCreated) {
  1036. handleSearchItemSelect(item.id, true);
  1037. } else if (!isSelected && isCurrentlyInCreated) {
  1038. setCreatedItems(prev => prev.filter(createdItem => createdItem.itemId !== item.id));
  1039. }
  1040. });
  1041. }
  1042. } else {
  1043. const previousIds = selectedSecondSearchItemIds;
  1044. setSelectedSecondSearchItemIds(ids);
  1045. const newlySelected = ids.filter(id => !previousIds.includes(id));
  1046. const newlyDeselected = previousIds.filter(id => !ids.includes(id));
  1047. newlySelected.forEach(id => {
  1048. if (!isItemInCreated(id as number)) {
  1049. handleSearchItemSelect(id as number, true);
  1050. }
  1051. });
  1052. newlyDeselected.forEach(id => {
  1053. setCreatedItems(prev => prev.filter(createdItem => createdItem.itemId !== id));
  1054. });
  1055. }
  1056. }, [selectedSecondSearchItemIds, secondSearchResults, isItemInCreated, handleSearchItemSelect]);
  1057. // Update the secondSearchItemColumns to add right alignment for Current Stock and Order Quantity
  1058. const secondSearchItemColumns: Column<SearchItemWithQty>[] = useMemo(() => [
  1059. {
  1060. name: "id",
  1061. label: "",
  1062. type: "checkbox",
  1063. disabled: (item) => isItemInCreated(item.id),
  1064. },
  1065. {
  1066. name: "label",
  1067. label: t("Item"),
  1068. renderCell: (item) => {
  1069. const parts = item.label.split(' - ');
  1070. const code = parts[0] || '';
  1071. const name = parts[1] || '';
  1072. return (
  1073. <Box>
  1074. <Typography variant="body2">
  1075. {name}
  1076. </Typography>
  1077. <Typography variant="caption" color="textSecondary">
  1078. {code}
  1079. </Typography>
  1080. </Box>
  1081. );
  1082. },
  1083. },
  1084. {
  1085. name: "currentStockBalance",
  1086. label: t("Current Stock"),
  1087. align: "right", // Add right alignment for the label
  1088. renderCell: (item) => {
  1089. const stockBalance = item.currentStockBalance || 0;
  1090. return (
  1091. <Box sx={{ display: 'flex', justifyContent: 'flex-end', width: '100%' }}>
  1092. <Typography
  1093. variant="body2"
  1094. color={stockBalance > 0 ? "success.main" : "error.main"}
  1095. sx={{
  1096. fontWeight: stockBalance > 0 ? 'bold' : 'normal',
  1097. textAlign: 'right' // Add right alignment for the value
  1098. }}
  1099. >
  1100. {stockBalance?.toLocaleString() }
  1101. </Typography>
  1102. </Box>
  1103. );
  1104. },
  1105. },
  1106. {
  1107. name: "uom",
  1108. label: t("Stock Unit"),
  1109. align: "right", // Add right alignment for the label
  1110. renderCell: (item) => (
  1111. <Box sx={{ display: 'flex', justifyContent: 'flex-end', width: '100%' }}>
  1112. <Typography sx={{ textAlign: 'right' }}> {/* Add right alignment for the value */}
  1113. {item.uom || "-"}
  1114. </Typography>
  1115. </Box>
  1116. ),
  1117. },
  1118. {
  1119. name: "qty",
  1120. label: t("Order Quantity"),
  1121. align: "right",
  1122. renderCell: (item) => (
  1123. <Box sx={{ display: 'flex', justifyContent: 'flex-end', width: '100%' }}>
  1124. <TextField
  1125. type="number"
  1126. size="small"
  1127. value={item.qty || ""}
  1128. onChange={(e) => {
  1129. const value = e.target.value;
  1130. // Only allow numbers
  1131. if (value === "" || /^\d+$/.test(value)) {
  1132. const numValue = value === "" ? null : Number(value);
  1133. handleSecondSearchQtyChange(item.id, numValue);
  1134. }
  1135. }}
  1136. inputProps={{
  1137. style: { textAlign: 'center' }
  1138. }}
  1139. sx={{
  1140. width: '80px',
  1141. '& .MuiInputBase-input': {
  1142. textAlign: 'center',
  1143. cursor: 'text'
  1144. }
  1145. }}
  1146. onBlur={(e) => {
  1147. const value = e.target.value;
  1148. const numValue = value === "" ? null : Number(value);
  1149. if (numValue !== null && numValue < 1) {
  1150. handleSecondSearchQtyChange(item.id, 1); // Enforce min value
  1151. }
  1152. }}
  1153. />
  1154. </Box>
  1155. ),
  1156. }
  1157. ], [t, isItemInCreated, handleSecondSearchQtyChange, groups]);
  1158. // 添加缺失的 handleSecondSearch 函数
  1159. const handleSecondSearch = useCallback((query: Record<string, any>) => {
  1160. console.log("Second search triggered with query:", query);
  1161. setSecondSearchQuery({ ...query });
  1162. setIsLoadingSecondSearch(true);
  1163. // Sync second search box info to form - ensure type value is correct
  1164. if (query.type) {
  1165. let correctType = query.type;
  1166. if (query.type === "consumable") {
  1167. correctType = "Consumable";
  1168. } else if (query.type === "material") {
  1169. correctType = "MATERIAL";
  1170. } else if (query.type === "jo") {
  1171. correctType = "JOB_ORDER";
  1172. }
  1173. formProps.setValue("type", correctType);
  1174. }
  1175. // Build search parameters for the new API
  1176. const searchParams: Record<string, any> = {};
  1177. if (query.code && query.code.trim()) {
  1178. searchParams.code = query.code.trim();
  1179. }
  1180. if (query.name && query.name.trim()) {
  1181. searchParams.name = query.name.trim();
  1182. }
  1183. if (query.type && query.type !== "All") {
  1184. searchParams.type = query.type;
  1185. }
  1186. // Add pagination parameters
  1187. searchParams.pageSize = 100;
  1188. searchParams.pageNum = 1;
  1189. // Use the new API
  1190. fetchItemsWithDetails(searchParams)
  1191. .then(response => {
  1192. try {
  1193. // Fix: Handle the response type correctly and safely
  1194. let itemsToTransform: any[] = [];
  1195. // Safely check and extract data from response
  1196. if (response && typeof response === 'object') {
  1197. if ('records' in response && Array.isArray((response as any).records)) {
  1198. itemsToTransform = (response as any).records;
  1199. } else if (Array.isArray(response)) {
  1200. itemsToTransform = response;
  1201. }
  1202. }
  1203. const transformedItems: SearchItemWithQty[] = itemsToTransform.map((item: any) => ({
  1204. id: item.id,
  1205. label: `${item.code} - ${item.name}`,
  1206. uomId: item.uomId,
  1207. uom: item.uom,
  1208. uomDesc: item.uomDesc,
  1209. currentStockBalance: item.currentStockBalance,
  1210. qty: null,
  1211. targetDate: undefined,
  1212. groupId: undefined,
  1213. }));
  1214. setSecondSearchResults(transformedItems);
  1215. setHasSearchedSecond(true);
  1216. } catch (error) {
  1217. console.error("Error processing response:", error);
  1218. setSecondSearchResults([]);
  1219. } finally {
  1220. setIsLoadingSecondSearch(false);
  1221. }
  1222. })
  1223. .catch(error => {
  1224. console.error("Error in second search:", error);
  1225. setSecondSearchResults([]);
  1226. setIsLoadingSecondSearch(false);
  1227. });
  1228. }, [formProps, t]);
  1229. /*
  1230. // Create a custom search box component that displays fields vertically
  1231. const VerticalSearchBox = ({ criteria, onSearch, onReset }: {
  1232. criteria: Criterion<any>[];
  1233. onSearch: (inputs: Record<string, any>) => void;
  1234. onReset?: () => void;
  1235. }) => {
  1236. const { t } = useTranslation("common");
  1237. const [inputs, setInputs] = useState<Record<string, any>>({});
  1238. const handleInputChange = (paramName: string, value: any) => {
  1239. setInputs(prev => ({ ...prev, [paramName]: value }));
  1240. };
  1241. const handleSearch = () => {
  1242. onSearch(inputs);
  1243. };
  1244. const handleReset = () => {
  1245. setInputs({});
  1246. onReset?.();
  1247. };
  1248. return (
  1249. <Card>
  1250. <CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
  1251. <Typography variant="overline">{t("Search Criteria")}</Typography>
  1252. <Grid container spacing={2} columns={{ xs: 12, sm: 12 }}>
  1253. {criteria.map((c) => {
  1254. return (
  1255. <Grid key={c.paramName} item xs={12}>
  1256. {c.type === "text" && (
  1257. <TextField
  1258. label={t(c.label)}
  1259. fullWidth
  1260. onChange={(e) => handleInputChange(c.paramName, e.target.value)}
  1261. value={inputs[c.paramName] || ""}
  1262. />
  1263. )}
  1264. {c.type === "autocomplete" && (
  1265. <Autocomplete
  1266. options={c.options || []}
  1267. getOptionLabel={(option: any) => option.label}
  1268. onChange={(_, value: any) => handleInputChange(c.paramName, value?.value || "")}
  1269. value={c.options?.find(option => option.value === inputs[c.paramName]) || null}
  1270. renderInput={(params) => (
  1271. <TextField
  1272. {...params}
  1273. label={t(c.label)}
  1274. fullWidth
  1275. />
  1276. )}
  1277. />
  1278. )}
  1279. </Grid>
  1280. );
  1281. })}
  1282. </Grid>
  1283. <Stack direction="row" spacing={2} sx={{ mt: 2 }}>
  1284. <Button
  1285. variant="text"
  1286. startIcon={<RestartAlt />}
  1287. onClick={handleReset}
  1288. >
  1289. {t("Reset")}
  1290. </Button>
  1291. <Button
  1292. variant="outlined"
  1293. startIcon={<Search />}
  1294. onClick={handleSearch}
  1295. >
  1296. {t("Search")}
  1297. </Button>
  1298. </Stack>
  1299. </CardContent>
  1300. </Card>
  1301. );
  1302. };
  1303. */
  1304. // Add pagination state for search results
  1305. const [searchResultsPagingController, setSearchResultsPagingController] = useState({
  1306. pageNum: 1,
  1307. pageSize: 10,
  1308. });
  1309. // Add pagination handlers for search results
  1310. const handleSearchResultsPageChange = useCallback((event: unknown, newPage: number) => {
  1311. const newPagingController = {
  1312. ...searchResultsPagingController,
  1313. pageNum: newPage + 1, // API uses 1-based pagination
  1314. };
  1315. setSearchResultsPagingController(newPagingController);
  1316. }, [searchResultsPagingController]);
  1317. const handleSearchResultsPageSizeChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
  1318. const newPageSize = parseInt(event.target.value, 10);
  1319. const newPagingController = {
  1320. pageNum: 1, // Reset to first page
  1321. pageSize: newPageSize,
  1322. };
  1323. setSearchResultsPagingController(newPagingController);
  1324. }, []);
  1325. const getValidationMessage = useCallback(() => {
  1326. const selectedItems = secondSearchResults.filter(item =>
  1327. selectedSecondSearchItemIds.includes(item.id)
  1328. );
  1329. const itemsWithoutGroup = selectedItems.filter(item =>
  1330. item.groupId === undefined || item.groupId === null
  1331. );
  1332. const itemsWithoutQty = selectedItems.filter(item =>
  1333. item.qty === null || item.qty === undefined || item.qty <= 0
  1334. );
  1335. if (itemsWithoutGroup.length > 0 && itemsWithoutQty.length > 0) {
  1336. return t("Please select group and enter quantity for all selected items");
  1337. } else if (itemsWithoutGroup.length > 0) {
  1338. return t("Please select group for all selected items");
  1339. } else if (itemsWithoutQty.length > 0) {
  1340. return t("Please enter quantity for all selected items");
  1341. }
  1342. return "";
  1343. }, [secondSearchResults, selectedSecondSearchItemIds, t]);
  1344. // Fix the handleAddSelectedToCreatedItems function to properly clear selections
  1345. const handleAddSelectedToCreatedItems = useCallback(() => {
  1346. const selectedItems = secondSearchResults.filter(item =>
  1347. selectedSecondSearchItemIds.includes(item.id)
  1348. );
  1349. // Add selected items to created items with their own group info
  1350. selectedItems.forEach(item => {
  1351. if (!isItemInCreated(item.id)) {
  1352. const newCreatedItem: CreatedItem = {
  1353. itemId: item.id,
  1354. itemName: item.label,
  1355. itemCode: item.label,
  1356. qty: item.qty || 1,
  1357. uom: item.uom || "",
  1358. uomId: item.uomId || 0,
  1359. uomDesc: item.uomDesc || "",
  1360. isSelected: true,
  1361. currentStockBalance: item.currentStockBalance,
  1362. targetDate: item.targetDate || targetDate,
  1363. groupId: item.groupId || undefined,
  1364. };
  1365. setCreatedItems(prev => [...prev, newCreatedItem]);
  1366. }
  1367. });
  1368. // Clear the selection
  1369. setSelectedSecondSearchItemIds([]);
  1370. // Remove the selected/added items from search results entirely
  1371. setSecondSearchResults(prev => prev.filter(item =>
  1372. !selectedSecondSearchItemIds.includes(item.id)
  1373. ));
  1374. }, [secondSearchResults, selectedSecondSearchItemIds, isItemInCreated, targetDate]);
  1375. // Add a validation function to check if selected items are valid
  1376. const areSelectedItemsValid = useCallback(() => {
  1377. const selectedItems = secondSearchResults.filter(item =>
  1378. selectedSecondSearchItemIds.includes(item.id)
  1379. );
  1380. return selectedItems.every(item =>
  1381. item.groupId !== undefined &&
  1382. item.groupId !== null &&
  1383. item.qty !== null &&
  1384. item.qty !== undefined &&
  1385. item.qty > 0
  1386. );
  1387. }, [secondSearchResults, selectedSecondSearchItemIds]);
  1388. // Move these handlers to the component level (outside of CustomSearchResultsTable)
  1389. // Handle individual checkbox change - ONLY select, don't add to created items
  1390. const handleIndividualCheckboxChange = useCallback((itemId: number, checked: boolean) => {
  1391. checkboxChangeCallCount++;
  1392. if (checked) {
  1393. // Add to selected IDs
  1394. setSelectedSecondSearchItemIds(prev => [...prev, itemId]);
  1395. // Set the item's group and targetDate to current group when selected
  1396. setSecondSearchResults(prev => {
  1397. const updatedResults = prev.map(item =>
  1398. item.id === itemId
  1399. ? {
  1400. ...item,
  1401. groupId: selectedGroup?.id || undefined,
  1402. targetDate: selectedGroup?.targetDate !== undefined && selectedGroup?.targetDate !== "" ? selectedGroup.targetDate : undefined
  1403. }
  1404. : item
  1405. );
  1406. // Check if should auto-add after state update
  1407. setTimeout(() => {
  1408. // Check if we're already processing this item
  1409. if (processingItems.has(itemId)) {
  1410. //alert(`Item ${itemId} is already being processed, skipping duplicate auto-add`);
  1411. return;
  1412. }
  1413. const updatedItem = updatedResults.find(i => i.id === itemId);
  1414. if (updatedItem) {
  1415. const isSelected = true; // We just selected it
  1416. const hasGroup = updatedItem.groupId !== undefined && updatedItem.groupId !== null;
  1417. const hasQty = updatedItem.qty !== null && updatedItem.qty !== undefined && updatedItem.qty > 0;
  1418. // Only auto-add if item has quantity (scenario 2: enter quantity first, then select)
  1419. if (isSelected && hasGroup && hasQty && !isItemInCreated(updatedItem.id)) {
  1420. // Mark this item as being processed
  1421. processingItems.add(itemId);
  1422. const newCreatedItem: CreatedItem = {
  1423. itemId: updatedItem.id,
  1424. itemName: updatedItem.label,
  1425. itemCode: updatedItem.label,
  1426. qty: updatedItem.qty || 1,
  1427. uom: updatedItem.uom || "",
  1428. uomId: updatedItem.uomId || 0,
  1429. uomDesc: updatedItem.uomDesc || "",
  1430. isSelected: true,
  1431. currentStockBalance: updatedItem.currentStockBalance,
  1432. targetDate: updatedItem.targetDate || targetDate,
  1433. groupId: updatedItem.groupId || undefined,
  1434. };
  1435. setCreatedItems(prev => [...prev, newCreatedItem]);
  1436. setSecondSearchResults(current => current.filter(searchItem => searchItem.id !== itemId));
  1437. setSelectedSecondSearchItemIds(current => current.filter(id => id !== itemId));
  1438. // Remove from processing set after a short delay
  1439. setTimeout(() => {
  1440. processingItems.delete(itemId);
  1441. }, 100);
  1442. }
  1443. // Show final debug info in one alert
  1444. /*
  1445. alert(`FINAL DEBUG INFO for item ${itemId}:
  1446. Function called ${checkboxChangeCallCount} times
  1447. Is Selected: ${isSelected}
  1448. Has Group: ${hasGroup}
  1449. Has Quantity: ${hasQty}
  1450. Quantity: ${updatedItem.qty}
  1451. Group ID: ${updatedItem.groupId}
  1452. Is Item In Created: ${isItemInCreated(updatedItem.id)}
  1453. Auto-add triggered: ${isSelected && hasGroup && hasQty && !isItemInCreated(updatedItem.id)}
  1454. Processing items: ${Array.from(processingItems).join(', ')}`);
  1455. */
  1456. }
  1457. }, 0);
  1458. return updatedResults;
  1459. });
  1460. } else {
  1461. // Remove from selected IDs
  1462. setSelectedSecondSearchItemIds(prev => prev.filter(id => id !== itemId));
  1463. // Clear the item's group and targetDate when deselected
  1464. setSecondSearchResults(prev => prev.map(item =>
  1465. item.id === itemId
  1466. ? {
  1467. ...item,
  1468. groupId: undefined,
  1469. targetDate: undefined
  1470. }
  1471. : item
  1472. ));
  1473. }
  1474. }, [selectedGroup, isItemInCreated, targetDate]);
  1475. // Handle select all checkbox for current page
  1476. const handleSelectAllOnPage = useCallback((checked: boolean, paginatedResults: SearchItemWithQty[]) => {
  1477. if (checked) {
  1478. // Select all items on current page that are not already in created items
  1479. const newSelectedIds = paginatedResults
  1480. .filter(item => !isItemInCreated(item.id))
  1481. .map(item => item.id);
  1482. setSelectedSecondSearchItemIds(prev => {
  1483. const existingIds = prev.filter(id => !paginatedResults.some(item => item.id === id));
  1484. return [...existingIds, ...newSelectedIds];
  1485. });
  1486. // Set group and targetDate for all selected items on current page
  1487. setSecondSearchResults(prev => prev.map(item =>
  1488. newSelectedIds.includes(item.id)
  1489. ? {
  1490. ...item,
  1491. groupId: selectedGroup?.id || undefined,
  1492. targetDate: selectedGroup?.targetDate !== undefined && selectedGroup.targetDate !== "" ? selectedGroup.targetDate : undefined
  1493. }
  1494. : item
  1495. ));
  1496. } else {
  1497. // Deselect all items on current page
  1498. const pageItemIds = paginatedResults.map(item => item.id);
  1499. setSelectedSecondSearchItemIds(prev => prev.filter(id => !pageItemIds.includes(id as number)));
  1500. // Clear group and targetDate for all deselected items on current page
  1501. setSecondSearchResults(prev => prev.map(item =>
  1502. pageItemIds.includes(item.id)
  1503. ? {
  1504. ...item,
  1505. groupId: undefined,
  1506. targetDate: undefined
  1507. }
  1508. : item
  1509. ));
  1510. }
  1511. }, [selectedGroup, isItemInCreated]);
  1512. // Update the CustomSearchResultsTable to use the handlers from component level
  1513. /*
  1514. const CustomSearchResultsTable = () => {
  1515. // Calculate pagination
  1516. const startIndex = (searchResultsPagingController.pageNum - 1) * searchResultsPagingController.pageSize;
  1517. const endIndex = startIndex + searchResultsPagingController.pageSize;
  1518. const paginatedResults = secondSearchResults.slice(startIndex, endIndex);
  1519. // Check if all items on current page are selected
  1520. const allSelectedOnPage = paginatedResults.length > 0 &&
  1521. paginatedResults.every(item => selectedSecondSearchItemIds.includes(item.id));
  1522. // Check if some items on current page are selected
  1523. const someSelectedOnPage = paginatedResults.some(item => selectedSecondSearchItemIds.includes(item.id));
  1524. return (
  1525. <>
  1526. <TableContainer component={Paper}>
  1527. <Table>
  1528. <TableHead>
  1529. <TableRow>
  1530. <TableCell padding="checkbox" sx={{ width: '80px', minWidth: '80px' }}>
  1531. {t("Selected")}
  1532. </TableCell>
  1533. <TableCell>
  1534. {t("Item")}
  1535. </TableCell>
  1536. <TableCell>
  1537. {t("Group")}
  1538. </TableCell>
  1539. <TableCell align="right">
  1540. {t("Current Stock")}
  1541. </TableCell>
  1542. <TableCell align="right">
  1543. {t("Stock Unit")}
  1544. </TableCell>
  1545. <TableCell align="right">
  1546. {t("Order Quantity")}
  1547. </TableCell>
  1548. <TableCell align="right">
  1549. {t("Target Date")}
  1550. </TableCell>
  1551. </TableRow>
  1552. </TableHead>
  1553. <TableBody>
  1554. {paginatedResults.length === 0 ? (
  1555. <TableRow>
  1556. <TableCell colSpan={12} align="center">
  1557. <Typography variant="body2" color="text.secondary">
  1558. {t("No data available")}
  1559. </Typography>
  1560. </TableCell>
  1561. </TableRow>
  1562. ) : (
  1563. paginatedResults.map((item) => (
  1564. <TableRow key={item.id}>
  1565. <TableCell padding="checkbox">
  1566. <Checkbox
  1567. checked={selectedSecondSearchItemIds.includes(item.id)}
  1568. onChange={(e) => handleIndividualCheckboxChange(item.id, e.target.checked)}
  1569. disabled={isItemInCreated(item.id)}
  1570. />
  1571. </TableCell>
  1572. <TableCell>
  1573. <Box>
  1574. <Typography variant="body2">
  1575. {item.label.split(' - ')[1] || item.label}
  1576. </Typography>
  1577. <Typography variant="caption" color="textSecondary">
  1578. {item.label.split(' - ')[0] || ''}
  1579. </Typography>
  1580. </Box>
  1581. </TableCell>
  1582. <TableCell>
  1583. <Typography variant="body2">
  1584. {(() => {
  1585. if (item.groupId) {
  1586. const group = groups.find(g => g.id === item.groupId);
  1587. return group?.name || "-";
  1588. }
  1589. return "-"; // Show "-" for unselected items
  1590. })()}
  1591. </Typography>
  1592. </TableCell>
  1593. <TableCell align="right">
  1594. <Typography
  1595. variant="body2"
  1596. color={item.currentStockBalance && item.currentStockBalance > 0 ? "success.main" : "error.main"}
  1597. sx={{ fontWeight: item.currentStockBalance && item.currentStockBalance > 0 ? 'bold' : 'normal' }}
  1598. >
  1599. {item.currentStockBalance || 0}
  1600. </Typography>
  1601. </TableCell>
  1602. <TableCell align="right">
  1603. <Typography variant="body2">
  1604. {item.uomDesc || "-"}
  1605. </Typography>
  1606. </TableCell>
  1607. <TableCell align="right">
  1608. <TextField
  1609. type="number"
  1610. size="small"
  1611. value={item.qty || ""}
  1612. onChange={(e) => {
  1613. const value = e.target.value;
  1614. // Only allow numbers
  1615. if (value === "" || /^\d+$/.test(value)) {
  1616. const numValue = value === "" ? null : Number(value);
  1617. handleSecondSearchQtyChange(item.id, numValue);
  1618. }
  1619. }}
  1620. onBlur={() => {
  1621. // Trigger auto-add check when user finishes input
  1622. handleQtyBlur(item.id);
  1623. }}
  1624. inputProps={{
  1625. style: { textAlign: 'center' }
  1626. }}
  1627. sx={{
  1628. width: '80px',
  1629. '& .MuiInputBase-input': {
  1630. textAlign: 'center',
  1631. cursor: 'text'
  1632. }
  1633. }}
  1634. />
  1635. </TableCell>
  1636. <TableCell align="right">
  1637. <Typography variant="body2">
  1638. {item.targetDate ? dayjs(item.targetDate).format(OUTPUT_DATE_FORMAT) : "-"}
  1639. </Typography>
  1640. </TableCell>
  1641. </TableRow>
  1642. ))
  1643. )}
  1644. </TableBody>
  1645. </Table>
  1646. </TableContainer>
  1647. <TablePagination
  1648. component="div"
  1649. count={secondSearchResults.length}
  1650. page={(searchResultsPagingController.pageNum - 1)} // Convert to 0-based for TablePagination
  1651. rowsPerPage={searchResultsPagingController.pageSize}
  1652. onPageChange={handleSearchResultsPageChange}
  1653. onRowsPerPageChange={handleSearchResultsPageSizeChange}
  1654. rowsPerPageOptions={[10, 25, 50]}
  1655. labelRowsPerPage={t("Rows per page")}
  1656. labelDisplayedRows={({ from, to, count }) =>
  1657. `${from}-${to} of ${count !== -1 ? count : `more than ${to}`}`
  1658. }
  1659. />
  1660. </>
  1661. );
  1662. };
  1663. */
  1664. // Add helper function to get group range text
  1665. const getGroupRangeText = useCallback(() => {
  1666. if (groups.length === 0) return "";
  1667. const firstGroup = groups[0];
  1668. const lastGroup = groups[groups.length - 1];
  1669. if (firstGroup.id === lastGroup.id) {
  1670. return `${t("First created group")}: ${firstGroup.name}`;
  1671. } else {
  1672. return `${t("First created group")}: ${firstGroup.name} - ${t("Latest created group")}: ${lastGroup.name}`;
  1673. }
  1674. }, [groups, t]);
  1675. return (
  1676. <FormProvider {...formProps}>
  1677. <Box
  1678. component="form"
  1679. onSubmit={formProps.handleSubmit(onSubmit)}
  1680. >
  1681. {/* First Search Box - Item Search with vertical layout */}
  1682. <Box sx={{ mt: 3, mb: 2 }}>
  1683. <Typography variant="h6" display="block" marginBlockEnd={1}>
  1684. {t("Search Items")}
  1685. </Typography>
  1686. <VerticalSearchBox
  1687. criteria={pickOrderSearchCriteria}
  1688. onSearch={handleSecondSearch}
  1689. onReset={handleSecondReset}
  1690. />
  1691. </Box>
  1692. {/* Create Group Section - 简化版本,不需要表单 */}
  1693. <Box sx={{ mt: 3, mb: 2 }}>
  1694. <Grid container spacing={2} alignItems="center">
  1695. <Grid item>
  1696. <Button
  1697. variant="outlined"
  1698. onClick={handleCreateGroup}
  1699. >
  1700. {t("Create New Group")}
  1701. </Button>
  1702. </Grid>
  1703. {groups.length > 0 && (
  1704. <>
  1705. <Grid item>
  1706. <Typography variant="body2">{t("Group")}:</Typography>
  1707. </Grid>
  1708. <Grid item>
  1709. <FormControl size="small" sx={{ minWidth: 200 }}>
  1710. <Select
  1711. value={selectedGroup?.id?.toString() || ""}
  1712. onChange={(e) => handleGroupChange(e.target.value)}
  1713. >
  1714. {groups.map((group) => (
  1715. <MenuItem key={group.id} value={group.id.toString()}>
  1716. {group.name}
  1717. </MenuItem>
  1718. ))}
  1719. </Select>
  1720. </FormControl>
  1721. </Grid>
  1722. {selectedGroup && (
  1723. <Grid item>
  1724. <LocalizationProvider dateAdapter={AdapterDayjs} adapterLocale="zh-hk">
  1725. <DatePicker
  1726. value={selectedGroup.targetDate && selectedGroup.targetDate !== "" ? dayjs(selectedGroup.targetDate) : null}
  1727. onChange={(date) => {
  1728. if (date) {
  1729. const formattedDate = date.format(INPUT_DATE_FORMAT);
  1730. handleGroupTargetDateChange(selectedGroup.id, formattedDate);
  1731. }
  1732. }}
  1733. slotProps={{
  1734. textField: {
  1735. size: "small",
  1736. label: t("Target Date"),
  1737. sx: { width: 200 }
  1738. },
  1739. }}
  1740. />
  1741. </LocalizationProvider>
  1742. </Grid>
  1743. )}
  1744. </>
  1745. )}
  1746. </Grid>
  1747. {/* Add group range text */}
  1748. {groups.length > 0 && (
  1749. <Box sx={{ mt: 1 }}>
  1750. <Typography variant="body2" color="text.secondary" sx={{ fontStyle: 'italic' }}>
  1751. {getGroupRangeText()}
  1752. </Typography>
  1753. </Box>
  1754. )}
  1755. </Box>
  1756. {/* Second Search Results - Use custom table like AssignAndRelease */}
  1757. {hasSearchedSecond && (
  1758. <Box sx={{ mt: 3 }}>
  1759. <Typography variant="h6" marginBlockEnd={2}>
  1760. {t("Search Results")} ({secondSearchResults.length})
  1761. </Typography>
  1762. {selectedSecondSearchItemIds.length > 0 && (
  1763. <Box sx={{ mb: 2 }}>
  1764. <Typography variant="body2" color="text.secondary" sx={{ fontStyle: 'italic' }}>
  1765. {t("Selected items will join above created group")}
  1766. </Typography>
  1767. </Box>
  1768. )}
  1769. {isLoadingSecondSearch ? (
  1770. <Typography>{t("Loading...")}</Typography>
  1771. ) : secondSearchResults.length === 0 ? (
  1772. <Typography color="textSecondary">{t("No results found")}</Typography>
  1773. ) : (
  1774. <SearchResultsTable
  1775. items={secondSearchResults}
  1776. selectedItemIds={selectedSecondSearchItemIds}
  1777. groups={groups}
  1778. onItemSelect={handleIndividualCheckboxChange}
  1779. onQtyChange={handleSecondSearchQtyChange}
  1780. onGroupChange={handleSearchItemGroupChange}
  1781. onQtyBlur={handleQtyBlur}
  1782. isItemInCreated={isItemInCreated}
  1783. pageNum={searchResultsPagingController.pageNum}
  1784. pageSize={searchResultsPagingController.pageSize}
  1785. onPageChange={handleSearchResultsPageChange}
  1786. onPageSizeChange={handleSearchResultsPageSizeChange}
  1787. />
  1788. )}
  1789. </Box>
  1790. )}
  1791. {/* Add Submit Button between tables */}
  1792. {/*
  1793. {hasSearchedSecond && secondSearchResults.length > 0 && selectedSecondSearchItemIds.length > 0 && (
  1794. <Box sx={{ mt: 2, mb: 2 }}>
  1795. <Box sx={{ display: 'flex', justifyContent: 'left', alignItems: 'center', gap: 2 }}>
  1796. <Button
  1797. variant="contained"
  1798. color="primary"
  1799. onClick={handleAddSelectedToCreatedItems}
  1800. disabled={selectedSecondSearchItemIds.length === 0 || !areSelectedItemsValid()}
  1801. sx={{ minWidth: 200 }}
  1802. >
  1803. {t("Add Selected Items to Created Items")} ({selectedSecondSearchItemIds.length})
  1804. </Button>
  1805. {selectedSecondSearchItemIds.length > 0 && !areSelectedItemsValid() && (
  1806. <Typography
  1807. variant="body2"
  1808. color="error.main"
  1809. sx={{ fontStyle: 'italic' }}
  1810. >
  1811. {getValidationMessage()}
  1812. </Typography>
  1813. )}
  1814. </Box>
  1815. </Box>
  1816. )}
  1817. */}
  1818. {/* 创建项目区域 - 修改Group列为可选择的 */}
  1819. {createdItems.length > 0 && (
  1820. <Box sx={{ mt: 3 }}>
  1821. <Typography variant="h6" marginBlockEnd={2}>
  1822. {t("Created Items")} ({createdItems.length})
  1823. </Typography>
  1824. <CreatedItemsTable
  1825. items={createdItems}
  1826. groups={groups}
  1827. onItemSelect={handleCreatedItemSelect}
  1828. onQtyChange={handleQtyChange}
  1829. onGroupChange={handleCreatedItemGroupChange}
  1830. pageNum={createdItemsPagingController.pageNum}
  1831. pageSize={createdItemsPagingController.pageSize}
  1832. onPageChange={handleCreatedItemsPageChange}
  1833. onPageSizeChange={handleCreatedItemsPageSizeChange}
  1834. />
  1835. </Box>
  1836. )}
  1837. {/* 操作按钮 */}
  1838. <Stack direction="row" justifyContent="flex-start" gap={1} sx={{ mt: 3 }}>
  1839. <Button
  1840. name="submit"
  1841. variant="contained"
  1842. startIcon={<Check />}
  1843. type="submit"
  1844. disabled={createdItems.filter(item => item.isSelected).length === 0}
  1845. >
  1846. {t("Create Pick Order")}
  1847. </Button>
  1848. <Button
  1849. name="reset"
  1850. variant="outlined"
  1851. onClick={handleReset}
  1852. >
  1853. {t("reset")}
  1854. </Button>
  1855. </Stack>
  1856. </Box>
  1857. </FormProvider>
  1858. );
  1859. };
  1860. export default NewCreateItem;