FPSMS-frontend
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
 
 

2054 рядки
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 } 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 || '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. </MenuItem>
  814. ))}
  815. </Select>
  816. </FormControl>
  817. </TableCell>
  818. <TableCell align="right">
  819. <Typography
  820. variant="body2"
  821. color={item.currentStockBalance && item.currentStockBalance > 0 ? "success.main" : "error.main"}
  822. >
  823. {item.currentStockBalance?.toLocaleString() || 0}
  824. </Typography>
  825. </TableCell>
  826. <TableCell align="right">
  827. <Typography variant="body2">{item.uomDesc}</Typography>
  828. </TableCell>
  829. <TableCell align="right">
  830. <TextField
  831. type="number"
  832. size="small"
  833. value={item.qty || ""}
  834. onChange={(e) => {
  835. const newQty = Number(e.target.value);
  836. handleQtyChange(item.itemId, newQty);
  837. }}
  838. inputProps={{
  839. min: 1,
  840. step: 1,
  841. style: { textAlign: 'center' }
  842. }}
  843. sx={{
  844. width: '80px',
  845. '& .MuiInputBase-input': {
  846. textAlign: 'center',
  847. cursor: 'text'
  848. }
  849. }}
  850. />
  851. </TableCell>
  852. <TableCell align="right">
  853. <Typography variant="body2">
  854. {item.targetDate&& item.targetDate !== "" ? new Date(item.targetDate).toLocaleDateString() : "-"}
  855. </Typography>
  856. </TableCell>
  857. </TableRow>
  858. ))
  859. )}
  860. </TableBody>
  861. </Table>
  862. </TableContainer>
  863. {/* Pagination for created items */}
  864. <TablePagination
  865. component="div"
  866. count={createdItems.length}
  867. page={(createdItemsPagingController.pageNum - 1)}
  868. rowsPerPage={createdItemsPagingController.pageSize}
  869. onPageChange={handleCreatedItemsPageChange}
  870. onRowsPerPageChange={handleCreatedItemsPageSizeChange}
  871. rowsPerPageOptions={[10, 25, 50]}
  872. labelRowsPerPage={t("Rows per page")}
  873. labelDisplayedRows={({ from, to, count }) =>
  874. `${from}-${to} of ${count !== -1 ? count : `more than ${to}`}`
  875. }
  876. />
  877. </>
  878. );
  879. };
  880. // Define columns for SearchResults
  881. const searchItemColumns: Column<SearchItemWithQty>[] = useMemo(() => [
  882. {
  883. name: "id",
  884. label: "",
  885. type: "checkbox",
  886. disabled: (item) => isItemInCreated(item.id), // Disable if already in created items
  887. },
  888. {
  889. name: "label",
  890. label: t("Item"),
  891. renderCell: (item) => {
  892. const parts = item.label.split(' - ');
  893. const code = parts[0] || '';
  894. const name = parts[1] || '';
  895. return (
  896. <Box>
  897. <Typography variant="body2">
  898. {name} {/* 显示项目名称 */}
  899. </Typography>
  900. <Typography variant="caption" color="textSecondary">
  901. {code} {/* 显示项目代码 */}
  902. </Typography>
  903. </Box>
  904. );
  905. },
  906. },
  907. {
  908. name: "qty",
  909. label: t("Order Quantity"),
  910. renderCell: (item) => (
  911. <TextField
  912. type="number"
  913. size="small"
  914. value={item.qty || ""} // Show empty string if qty is null
  915. onChange={(e) => {
  916. const value = e.target.value;
  917. const numValue = value === "" ? null : Number(value);
  918. handleSearchQtyChange(item.id, numValue);
  919. }}
  920. inputProps={{
  921. min: 1,
  922. step: 1,
  923. style: { textAlign: 'center' } // Center the text
  924. }}
  925. sx={{
  926. width: '80px',
  927. '& .MuiInputBase-input': {
  928. textAlign: 'center',
  929. cursor: 'text'
  930. }
  931. }}
  932. />
  933. ),
  934. },
  935. {
  936. name: "currentStockBalance",
  937. label: t("Current Stock"),
  938. renderCell: (item) => {
  939. const stockBalance = item.currentStockBalance || 0;
  940. return (
  941. <Typography
  942. variant="body2"
  943. color={stockBalance > 0 ? "success.main" : "error.main"}
  944. sx={{ fontWeight: stockBalance > 0 ? 'bold' : 'normal' }}
  945. >
  946. {stockBalance?.toLocaleString() }
  947. </Typography>
  948. );
  949. },
  950. },
  951. {
  952. name: "targetDate",
  953. label: t("Target Date"),
  954. renderCell: (item) => (
  955. <Typography variant="body2">
  956. {item.targetDate ? new Date(item.targetDate).toLocaleDateString() : "-"}
  957. </Typography>
  958. ),
  959. },
  960. {
  961. name: "uom",
  962. label: t("Stock Unit"),
  963. renderCell: (item) => item.uom || "-",
  964. },
  965. ], [t, isItemInCreated, handleSearchQtyChange]);
  966. // 修改搜索条件为3行,每行一个 - 确保SearchBox组件能正确处理
  967. const pickOrderSearchCriteria: Criterion<any>[] = useMemo(
  968. () => [
  969. {
  970. label: t("Item Code"),
  971. paramName: "code",
  972. type: "text"
  973. },
  974. {
  975. label: t("Item Name"),
  976. paramName: "name",
  977. type: "text"
  978. },
  979. {
  980. label: t("Product Type"),
  981. paramName: "type",
  982. type: "autocomplete",
  983. options: [
  984. { value: "Consumable", label: t("Consumable") },
  985. { value: "MATERIAL", label: t("Material") },
  986. { value: "End_product", label: t("End Product") }
  987. ],
  988. },
  989. ],
  990. [t],
  991. );
  992. // 添加重置函数
  993. const handleSecondReset = useCallback(() => {
  994. console.log("Second search reset");
  995. setSecondSearchQuery({});
  996. setSecondSearchResults([]);
  997. setHasSearchedSecond(false);
  998. // 清空表单中的类型,但保留今天的日期
  999. formProps.setValue("type", "");
  1000. const today = dayjs().format(INPUT_DATE_FORMAT);
  1001. formProps.setValue("targetDate", today);
  1002. }, [formProps]);
  1003. // 添加数量变更处理函数
  1004. const handleSecondSearchQtyChange = useCallback((itemId: number, newQty: number | null) => {
  1005. setSecondSearchResults(prev =>
  1006. prev.map(item =>
  1007. item.id === itemId ? { ...item, qty: newQty } : item
  1008. )
  1009. );
  1010. // Don't auto-add here - only on blur event
  1011. }, []);
  1012. // Add checkbox change handler for second search
  1013. const handleSecondSearchCheckboxChange = useCallback((ids: (string | number)[] | ((prev: (string | number)[]) => (string | number)[])) => {
  1014. if (typeof ids === 'function') {
  1015. const newIds = ids(selectedSecondSearchItemIds);
  1016. setSelectedSecondSearchItemIds(newIds);
  1017. // 处理全选逻辑 - 选择所有搜索结果,不仅仅是当前页面
  1018. if (newIds.length === secondSearchResults.length) {
  1019. // 全选:将所有搜索结果添加到创建项目
  1020. secondSearchResults.forEach(item => {
  1021. if (!isItemInCreated(item.id)) {
  1022. handleSearchItemSelect(item.id, true);
  1023. }
  1024. });
  1025. } else {
  1026. // 部分选择:只处理当前页面的选择
  1027. secondSearchResults.forEach(item => {
  1028. const isSelected = newIds.includes(item.id);
  1029. const isCurrentlyInCreated = isItemInCreated(item.id);
  1030. if (isSelected && !isCurrentlyInCreated) {
  1031. handleSearchItemSelect(item.id, true);
  1032. } else if (!isSelected && isCurrentlyInCreated) {
  1033. setCreatedItems(prev => prev.filter(createdItem => createdItem.itemId !== item.id));
  1034. }
  1035. });
  1036. }
  1037. } else {
  1038. const previousIds = selectedSecondSearchItemIds;
  1039. setSelectedSecondSearchItemIds(ids);
  1040. const newlySelected = ids.filter(id => !previousIds.includes(id));
  1041. const newlyDeselected = previousIds.filter(id => !ids.includes(id));
  1042. newlySelected.forEach(id => {
  1043. if (!isItemInCreated(id as number)) {
  1044. handleSearchItemSelect(id as number, true);
  1045. }
  1046. });
  1047. newlyDeselected.forEach(id => {
  1048. setCreatedItems(prev => prev.filter(createdItem => createdItem.itemId !== id));
  1049. });
  1050. }
  1051. }, [selectedSecondSearchItemIds, secondSearchResults, isItemInCreated, handleSearchItemSelect]);
  1052. // Update the secondSearchItemColumns to add right alignment for Current Stock and Order Quantity
  1053. const secondSearchItemColumns: Column<SearchItemWithQty>[] = useMemo(() => [
  1054. {
  1055. name: "id",
  1056. label: "",
  1057. type: "checkbox",
  1058. disabled: (item) => isItemInCreated(item.id),
  1059. },
  1060. {
  1061. name: "label",
  1062. label: t("Item"),
  1063. renderCell: (item) => {
  1064. const parts = item.label.split(' - ');
  1065. const code = parts[0] || '';
  1066. const name = parts[1] || '';
  1067. return (
  1068. <Box>
  1069. <Typography variant="body2">
  1070. {name}
  1071. </Typography>
  1072. <Typography variant="caption" color="textSecondary">
  1073. {code}
  1074. </Typography>
  1075. </Box>
  1076. );
  1077. },
  1078. },
  1079. {
  1080. name: "currentStockBalance",
  1081. label: t("Current Stock"),
  1082. align: "right", // Add right alignment for the label
  1083. renderCell: (item) => {
  1084. const stockBalance = item.currentStockBalance || 0;
  1085. return (
  1086. <Box sx={{ display: 'flex', justifyContent: 'flex-end', width: '100%' }}>
  1087. <Typography
  1088. variant="body2"
  1089. color={stockBalance > 0 ? "success.main" : "error.main"}
  1090. sx={{
  1091. fontWeight: stockBalance > 0 ? 'bold' : 'normal',
  1092. textAlign: 'right' // Add right alignment for the value
  1093. }}
  1094. >
  1095. {stockBalance?.toLocaleString() }
  1096. </Typography>
  1097. </Box>
  1098. );
  1099. },
  1100. },
  1101. {
  1102. name: "uom",
  1103. label: t("Stock Unit"),
  1104. align: "right", // Add right alignment for the label
  1105. renderCell: (item) => (
  1106. <Box sx={{ display: 'flex', justifyContent: 'flex-end', width: '100%' }}>
  1107. <Typography sx={{ textAlign: 'right' }}> {/* Add right alignment for the value */}
  1108. {item.uom || "-"}
  1109. </Typography>
  1110. </Box>
  1111. ),
  1112. },
  1113. {
  1114. name: "qty",
  1115. label: t("Order Quantity"),
  1116. align: "right",
  1117. renderCell: (item) => (
  1118. <Box sx={{ display: 'flex', justifyContent: 'flex-end', width: '100%' }}>
  1119. <TextField
  1120. type="number"
  1121. size="small"
  1122. value={item.qty || ""}
  1123. onChange={(e) => {
  1124. const value = e.target.value;
  1125. // Only allow numbers
  1126. if (value === "" || /^\d+$/.test(value)) {
  1127. const numValue = value === "" ? null : Number(value);
  1128. handleSecondSearchQtyChange(item.id, numValue);
  1129. }
  1130. }}
  1131. inputProps={{
  1132. style: { textAlign: 'center' }
  1133. }}
  1134. sx={{
  1135. width: '80px',
  1136. '& .MuiInputBase-input': {
  1137. textAlign: 'center',
  1138. cursor: 'text'
  1139. }
  1140. }}
  1141. onBlur={(e) => {
  1142. const value = e.target.value;
  1143. const numValue = value === "" ? null : Number(value);
  1144. if (numValue !== null && numValue < 1) {
  1145. handleSecondSearchQtyChange(item.id, 1); // Enforce min value
  1146. }
  1147. }}
  1148. />
  1149. </Box>
  1150. ),
  1151. }
  1152. ], [t, isItemInCreated, handleSecondSearchQtyChange, groups]);
  1153. // 添加缺失的 handleSecondSearch 函数
  1154. const handleSecondSearch = useCallback((query: Record<string, any>) => {
  1155. console.log("Second search triggered with query:", query);
  1156. setSecondSearchQuery({ ...query });
  1157. setIsLoadingSecondSearch(true);
  1158. // Sync second search box info to form - ensure type value is correct
  1159. if (query.type) {
  1160. let correctType = query.type;
  1161. if (query.type === "consumable") {
  1162. correctType = "Consumable";
  1163. } else if (query.type === "material") {
  1164. correctType = "MATERIAL";
  1165. } else if (query.type === "jo") {
  1166. correctType = "JOB_ORDER";
  1167. }
  1168. formProps.setValue("type", correctType);
  1169. }
  1170. // Build search parameters for the new API
  1171. const searchParams: Record<string, any> = {};
  1172. if (query.code && query.code.trim()) {
  1173. searchParams.code = query.code.trim();
  1174. }
  1175. if (query.name && query.name.trim()) {
  1176. searchParams.name = query.name.trim();
  1177. }
  1178. if (query.type && query.type !== "All") {
  1179. searchParams.type = query.type;
  1180. }
  1181. // Add pagination parameters
  1182. searchParams.pageSize = 100;
  1183. searchParams.pageNum = 1;
  1184. // Use the new API
  1185. fetchItemsWithDetails(searchParams)
  1186. .then(response => {
  1187. try {
  1188. // Fix: Handle the response type correctly and safely
  1189. let itemsToTransform: any[] = [];
  1190. // Safely check and extract data from response
  1191. if (response && typeof response === 'object') {
  1192. if ('records' in response && Array.isArray((response as any).records)) {
  1193. itemsToTransform = (response as any).records;
  1194. } else if (Array.isArray(response)) {
  1195. itemsToTransform = response;
  1196. }
  1197. }
  1198. const transformedItems: SearchItemWithQty[] = itemsToTransform.map((item: any) => ({
  1199. id: item.id,
  1200. label: `${item.code} - ${item.name}`,
  1201. uomId: item.uomId,
  1202. uom: item.uom,
  1203. uomDesc: item.uomDesc,
  1204. currentStockBalance: item.currentStockBalance,
  1205. qty: null,
  1206. targetDate: undefined,
  1207. groupId: undefined,
  1208. }));
  1209. setSecondSearchResults(transformedItems);
  1210. setHasSearchedSecond(true);
  1211. } catch (error) {
  1212. console.error("Error processing response:", error);
  1213. setSecondSearchResults([]);
  1214. } finally {
  1215. setIsLoadingSecondSearch(false);
  1216. }
  1217. })
  1218. .catch(error => {
  1219. console.error("Error in second search:", error);
  1220. setSecondSearchResults([]);
  1221. setIsLoadingSecondSearch(false);
  1222. });
  1223. }, [formProps, t]);
  1224. /*
  1225. // Create a custom search box component that displays fields vertically
  1226. const VerticalSearchBox = ({ criteria, onSearch, onReset }: {
  1227. criteria: Criterion<any>[];
  1228. onSearch: (inputs: Record<string, any>) => void;
  1229. onReset?: () => void;
  1230. }) => {
  1231. const { t } = useTranslation("common");
  1232. const [inputs, setInputs] = useState<Record<string, any>>({});
  1233. const handleInputChange = (paramName: string, value: any) => {
  1234. setInputs(prev => ({ ...prev, [paramName]: value }));
  1235. };
  1236. const handleSearch = () => {
  1237. onSearch(inputs);
  1238. };
  1239. const handleReset = () => {
  1240. setInputs({});
  1241. onReset?.();
  1242. };
  1243. return (
  1244. <Card>
  1245. <CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
  1246. <Typography variant="overline">{t("Search Criteria")}</Typography>
  1247. <Grid container spacing={2} columns={{ xs: 12, sm: 12 }}>
  1248. {criteria.map((c) => {
  1249. return (
  1250. <Grid key={c.paramName} item xs={12}>
  1251. {c.type === "text" && (
  1252. <TextField
  1253. label={t(c.label)}
  1254. fullWidth
  1255. onChange={(e) => handleInputChange(c.paramName, e.target.value)}
  1256. value={inputs[c.paramName] || ""}
  1257. />
  1258. )}
  1259. {c.type === "autocomplete" && (
  1260. <Autocomplete
  1261. options={c.options || []}
  1262. getOptionLabel={(option: any) => option.label}
  1263. onChange={(_, value: any) => handleInputChange(c.paramName, value?.value || "")}
  1264. value={c.options?.find(option => option.value === inputs[c.paramName]) || null}
  1265. renderInput={(params) => (
  1266. <TextField
  1267. {...params}
  1268. label={t(c.label)}
  1269. fullWidth
  1270. />
  1271. )}
  1272. />
  1273. )}
  1274. </Grid>
  1275. );
  1276. })}
  1277. </Grid>
  1278. <Stack direction="row" spacing={2} sx={{ mt: 2 }}>
  1279. <Button
  1280. variant="text"
  1281. startIcon={<RestartAlt />}
  1282. onClick={handleReset}
  1283. >
  1284. {t("Reset")}
  1285. </Button>
  1286. <Button
  1287. variant="outlined"
  1288. startIcon={<Search />}
  1289. onClick={handleSearch}
  1290. >
  1291. {t("Search")}
  1292. </Button>
  1293. </Stack>
  1294. </CardContent>
  1295. </Card>
  1296. );
  1297. };
  1298. */
  1299. // Add pagination state for search results
  1300. const [searchResultsPagingController, setSearchResultsPagingController] = useState({
  1301. pageNum: 1,
  1302. pageSize: 10,
  1303. });
  1304. // Add pagination handlers for search results
  1305. const handleSearchResultsPageChange = useCallback((event: unknown, newPage: number) => {
  1306. const newPagingController = {
  1307. ...searchResultsPagingController,
  1308. pageNum: newPage + 1, // API uses 1-based pagination
  1309. };
  1310. setSearchResultsPagingController(newPagingController);
  1311. }, [searchResultsPagingController]);
  1312. const handleSearchResultsPageSizeChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
  1313. const newPageSize = parseInt(event.target.value, 10);
  1314. const newPagingController = {
  1315. pageNum: 1, // Reset to first page
  1316. pageSize: newPageSize,
  1317. };
  1318. setSearchResultsPagingController(newPagingController);
  1319. }, []);
  1320. const getValidationMessage = useCallback(() => {
  1321. const selectedItems = secondSearchResults.filter(item =>
  1322. selectedSecondSearchItemIds.includes(item.id)
  1323. );
  1324. const itemsWithoutGroup = selectedItems.filter(item =>
  1325. item.groupId === undefined || item.groupId === null
  1326. );
  1327. const itemsWithoutQty = selectedItems.filter(item =>
  1328. item.qty === null || item.qty === undefined || item.qty <= 0
  1329. );
  1330. if (itemsWithoutGroup.length > 0 && itemsWithoutQty.length > 0) {
  1331. return t("Please select group and enter quantity for all selected items");
  1332. } else if (itemsWithoutGroup.length > 0) {
  1333. return t("Please select group for all selected items");
  1334. } else if (itemsWithoutQty.length > 0) {
  1335. return t("Please enter quantity for all selected items");
  1336. }
  1337. return "";
  1338. }, [secondSearchResults, selectedSecondSearchItemIds, t]);
  1339. // Fix the handleAddSelectedToCreatedItems function to properly clear selections
  1340. const handleAddSelectedToCreatedItems = useCallback(() => {
  1341. const selectedItems = secondSearchResults.filter(item =>
  1342. selectedSecondSearchItemIds.includes(item.id)
  1343. );
  1344. // Add selected items to created items with their own group info
  1345. selectedItems.forEach(item => {
  1346. if (!isItemInCreated(item.id)) {
  1347. const newCreatedItem: CreatedItem = {
  1348. itemId: item.id,
  1349. itemName: item.label,
  1350. itemCode: item.label,
  1351. qty: item.qty || 1,
  1352. uom: item.uom || "",
  1353. uomId: item.uomId || 0,
  1354. uomDesc: item.uomDesc || "",
  1355. isSelected: true,
  1356. currentStockBalance: item.currentStockBalance,
  1357. targetDate: item.targetDate || targetDate,
  1358. groupId: item.groupId || undefined,
  1359. };
  1360. setCreatedItems(prev => [...prev, newCreatedItem]);
  1361. }
  1362. });
  1363. // Clear the selection
  1364. setSelectedSecondSearchItemIds([]);
  1365. // Remove the selected/added items from search results entirely
  1366. setSecondSearchResults(prev => prev.filter(item =>
  1367. !selectedSecondSearchItemIds.includes(item.id)
  1368. ));
  1369. }, [secondSearchResults, selectedSecondSearchItemIds, isItemInCreated, targetDate]);
  1370. // Add a validation function to check if selected items are valid
  1371. const areSelectedItemsValid = useCallback(() => {
  1372. const selectedItems = secondSearchResults.filter(item =>
  1373. selectedSecondSearchItemIds.includes(item.id)
  1374. );
  1375. return selectedItems.every(item =>
  1376. item.groupId !== undefined &&
  1377. item.groupId !== null &&
  1378. item.qty !== null &&
  1379. item.qty !== undefined &&
  1380. item.qty > 0
  1381. );
  1382. }, [secondSearchResults, selectedSecondSearchItemIds]);
  1383. // Move these handlers to the component level (outside of CustomSearchResultsTable)
  1384. // Handle individual checkbox change - ONLY select, don't add to created items
  1385. const handleIndividualCheckboxChange = useCallback((itemId: number, checked: boolean) => {
  1386. checkboxChangeCallCount++;
  1387. if (checked) {
  1388. // Add to selected IDs
  1389. setSelectedSecondSearchItemIds(prev => [...prev, itemId]);
  1390. // Set the item's group and targetDate to current group when selected
  1391. setSecondSearchResults(prev => {
  1392. const updatedResults = prev.map(item =>
  1393. item.id === itemId
  1394. ? {
  1395. ...item,
  1396. groupId: selectedGroup?.id || undefined,
  1397. targetDate: selectedGroup?.targetDate !== undefined && selectedGroup?.targetDate !== "" ? selectedGroup.targetDate : undefined
  1398. }
  1399. : item
  1400. );
  1401. // Check if should auto-add after state update
  1402. setTimeout(() => {
  1403. // Check if we're already processing this item
  1404. if (processingItems.has(itemId)) {
  1405. //alert(`Item ${itemId} is already being processed, skipping duplicate auto-add`);
  1406. return;
  1407. }
  1408. const updatedItem = updatedResults.find(i => i.id === itemId);
  1409. if (updatedItem) {
  1410. const isSelected = true; // We just selected it
  1411. const hasGroup = updatedItem.groupId !== undefined && updatedItem.groupId !== null;
  1412. const hasQty = updatedItem.qty !== null && updatedItem.qty !== undefined && updatedItem.qty > 0;
  1413. // Only auto-add if item has quantity (scenario 2: enter quantity first, then select)
  1414. if (isSelected && hasGroup && hasQty && !isItemInCreated(updatedItem.id)) {
  1415. // Mark this item as being processed
  1416. processingItems.add(itemId);
  1417. const newCreatedItem: CreatedItem = {
  1418. itemId: updatedItem.id,
  1419. itemName: updatedItem.label,
  1420. itemCode: updatedItem.label,
  1421. qty: updatedItem.qty || 1,
  1422. uom: updatedItem.uom || "",
  1423. uomId: updatedItem.uomId || 0,
  1424. uomDesc: updatedItem.uomDesc || "",
  1425. isSelected: true,
  1426. currentStockBalance: updatedItem.currentStockBalance,
  1427. targetDate: updatedItem.targetDate || targetDate,
  1428. groupId: updatedItem.groupId || undefined,
  1429. };
  1430. setCreatedItems(prev => [...prev, newCreatedItem]);
  1431. setSecondSearchResults(current => current.filter(searchItem => searchItem.id !== itemId));
  1432. setSelectedSecondSearchItemIds(current => current.filter(id => id !== itemId));
  1433. // Remove from processing set after a short delay
  1434. setTimeout(() => {
  1435. processingItems.delete(itemId);
  1436. }, 100);
  1437. }
  1438. // Show final debug info in one alert
  1439. /*
  1440. alert(`FINAL DEBUG INFO for item ${itemId}:
  1441. Function called ${checkboxChangeCallCount} times
  1442. Is Selected: ${isSelected}
  1443. Has Group: ${hasGroup}
  1444. Has Quantity: ${hasQty}
  1445. Quantity: ${updatedItem.qty}
  1446. Group ID: ${updatedItem.groupId}
  1447. Is Item In Created: ${isItemInCreated(updatedItem.id)}
  1448. Auto-add triggered: ${isSelected && hasGroup && hasQty && !isItemInCreated(updatedItem.id)}
  1449. Processing items: ${Array.from(processingItems).join(', ')}`);
  1450. */
  1451. }
  1452. }, 0);
  1453. return updatedResults;
  1454. });
  1455. } else {
  1456. // Remove from selected IDs
  1457. setSelectedSecondSearchItemIds(prev => prev.filter(id => id !== itemId));
  1458. // Clear the item's group and targetDate when deselected
  1459. setSecondSearchResults(prev => prev.map(item =>
  1460. item.id === itemId
  1461. ? {
  1462. ...item,
  1463. groupId: undefined,
  1464. targetDate: undefined
  1465. }
  1466. : item
  1467. ));
  1468. }
  1469. }, [selectedGroup, isItemInCreated, targetDate]);
  1470. // Handle select all checkbox for current page
  1471. const handleSelectAllOnPage = useCallback((checked: boolean, paginatedResults: SearchItemWithQty[]) => {
  1472. if (checked) {
  1473. // Select all items on current page that are not already in created items
  1474. const newSelectedIds = paginatedResults
  1475. .filter(item => !isItemInCreated(item.id))
  1476. .map(item => item.id);
  1477. setSelectedSecondSearchItemIds(prev => {
  1478. const existingIds = prev.filter(id => !paginatedResults.some(item => item.id === id));
  1479. return [...existingIds, ...newSelectedIds];
  1480. });
  1481. // Set group and targetDate for all selected items on current page
  1482. setSecondSearchResults(prev => prev.map(item =>
  1483. newSelectedIds.includes(item.id)
  1484. ? {
  1485. ...item,
  1486. groupId: selectedGroup?.id || undefined,
  1487. targetDate: selectedGroup?.targetDate !== undefined && selectedGroup.targetDate !== "" ? selectedGroup.targetDate : undefined
  1488. }
  1489. : item
  1490. ));
  1491. } else {
  1492. // Deselect all items on current page
  1493. const pageItemIds = paginatedResults.map(item => item.id);
  1494. setSelectedSecondSearchItemIds(prev => prev.filter(id => !pageItemIds.includes(id as number)));
  1495. // Clear group and targetDate for all deselected items on current page
  1496. setSecondSearchResults(prev => prev.map(item =>
  1497. pageItemIds.includes(item.id)
  1498. ? {
  1499. ...item,
  1500. groupId: undefined,
  1501. targetDate: undefined
  1502. }
  1503. : item
  1504. ));
  1505. }
  1506. }, [selectedGroup, isItemInCreated]);
  1507. // Update the CustomSearchResultsTable to use the handlers from component level
  1508. /*
  1509. const CustomSearchResultsTable = () => {
  1510. // Calculate pagination
  1511. const startIndex = (searchResultsPagingController.pageNum - 1) * searchResultsPagingController.pageSize;
  1512. const endIndex = startIndex + searchResultsPagingController.pageSize;
  1513. const paginatedResults = secondSearchResults.slice(startIndex, endIndex);
  1514. // Check if all items on current page are selected
  1515. const allSelectedOnPage = paginatedResults.length > 0 &&
  1516. paginatedResults.every(item => selectedSecondSearchItemIds.includes(item.id));
  1517. // Check if some items on current page are selected
  1518. const someSelectedOnPage = paginatedResults.some(item => selectedSecondSearchItemIds.includes(item.id));
  1519. return (
  1520. <>
  1521. <TableContainer component={Paper}>
  1522. <Table>
  1523. <TableHead>
  1524. <TableRow>
  1525. <TableCell padding="checkbox" sx={{ width: '80px', minWidth: '80px' }}>
  1526. {t("Selected")}
  1527. </TableCell>
  1528. <TableCell>
  1529. {t("Item")}
  1530. </TableCell>
  1531. <TableCell>
  1532. {t("Group")}
  1533. </TableCell>
  1534. <TableCell align="right">
  1535. {t("Current Stock")}
  1536. </TableCell>
  1537. <TableCell align="right">
  1538. {t("Stock Unit")}
  1539. </TableCell>
  1540. <TableCell align="right">
  1541. {t("Order Quantity")}
  1542. </TableCell>
  1543. <TableCell align="right">
  1544. {t("Target Date")}
  1545. </TableCell>
  1546. </TableRow>
  1547. </TableHead>
  1548. <TableBody>
  1549. {paginatedResults.length === 0 ? (
  1550. <TableRow>
  1551. <TableCell colSpan={12} align="center">
  1552. <Typography variant="body2" color="text.secondary">
  1553. {t("No data available")}
  1554. </Typography>
  1555. </TableCell>
  1556. </TableRow>
  1557. ) : (
  1558. paginatedResults.map((item) => (
  1559. <TableRow key={item.id}>
  1560. <TableCell padding="checkbox">
  1561. <Checkbox
  1562. checked={selectedSecondSearchItemIds.includes(item.id)}
  1563. onChange={(e) => handleIndividualCheckboxChange(item.id, e.target.checked)}
  1564. disabled={isItemInCreated(item.id)}
  1565. />
  1566. </TableCell>
  1567. <TableCell>
  1568. <Box>
  1569. <Typography variant="body2">
  1570. {item.label.split(' - ')[1] || item.label}
  1571. </Typography>
  1572. <Typography variant="caption" color="textSecondary">
  1573. {item.label.split(' - ')[0] || ''}
  1574. </Typography>
  1575. </Box>
  1576. </TableCell>
  1577. <TableCell>
  1578. <Typography variant="body2">
  1579. {(() => {
  1580. if (item.groupId) {
  1581. const group = groups.find(g => g.id === item.groupId);
  1582. return group?.name || "-";
  1583. }
  1584. return "-"; // Show "-" for unselected items
  1585. })()}
  1586. </Typography>
  1587. </TableCell>
  1588. <TableCell align="right">
  1589. <Typography
  1590. variant="body2"
  1591. color={item.currentStockBalance && item.currentStockBalance > 0 ? "success.main" : "error.main"}
  1592. sx={{ fontWeight: item.currentStockBalance && item.currentStockBalance > 0 ? 'bold' : 'normal' }}
  1593. >
  1594. {item.currentStockBalance || 0}
  1595. </Typography>
  1596. </TableCell>
  1597. <TableCell align="right">
  1598. <Typography variant="body2">
  1599. {item.uomDesc || "-"}
  1600. </Typography>
  1601. </TableCell>
  1602. <TableCell align="right">
  1603. <TextField
  1604. type="number"
  1605. size="small"
  1606. value={item.qty || ""}
  1607. onChange={(e) => {
  1608. const value = e.target.value;
  1609. // Only allow numbers
  1610. if (value === "" || /^\d+$/.test(value)) {
  1611. const numValue = value === "" ? null : Number(value);
  1612. handleSecondSearchQtyChange(item.id, numValue);
  1613. }
  1614. }}
  1615. onBlur={() => {
  1616. // Trigger auto-add check when user finishes input
  1617. handleQtyBlur(item.id);
  1618. }}
  1619. inputProps={{
  1620. style: { textAlign: 'center' }
  1621. }}
  1622. sx={{
  1623. width: '80px',
  1624. '& .MuiInputBase-input': {
  1625. textAlign: 'center',
  1626. cursor: 'text'
  1627. }
  1628. }}
  1629. />
  1630. </TableCell>
  1631. <TableCell align="right">
  1632. <Typography variant="body2">
  1633. {item.targetDate ? new Date(item.targetDate).toLocaleDateString() : "-"}
  1634. </Typography>
  1635. </TableCell>
  1636. </TableRow>
  1637. ))
  1638. )}
  1639. </TableBody>
  1640. </Table>
  1641. </TableContainer>
  1642. <TablePagination
  1643. component="div"
  1644. count={secondSearchResults.length}
  1645. page={(searchResultsPagingController.pageNum - 1)} // Convert to 0-based for TablePagination
  1646. rowsPerPage={searchResultsPagingController.pageSize}
  1647. onPageChange={handleSearchResultsPageChange}
  1648. onRowsPerPageChange={handleSearchResultsPageSizeChange}
  1649. rowsPerPageOptions={[10, 25, 50]}
  1650. labelRowsPerPage={t("Rows per page")}
  1651. labelDisplayedRows={({ from, to, count }) =>
  1652. `${from}-${to} of ${count !== -1 ? count : `more than ${to}`}`
  1653. }
  1654. />
  1655. </>
  1656. );
  1657. };
  1658. */
  1659. // Add helper function to get group range text
  1660. const getGroupRangeText = useCallback(() => {
  1661. if (groups.length === 0) return "";
  1662. const firstGroup = groups[0];
  1663. const lastGroup = groups[groups.length - 1];
  1664. if (firstGroup.id === lastGroup.id) {
  1665. return `${t("First created group")}: ${firstGroup.name}`;
  1666. } else {
  1667. return `${t("First created group")}: ${firstGroup.name} - ${t("Latest created group")}: ${lastGroup.name}`;
  1668. }
  1669. }, [groups, t]);
  1670. return (
  1671. <FormProvider {...formProps}>
  1672. <Box
  1673. component="form"
  1674. onSubmit={formProps.handleSubmit(onSubmit)}
  1675. >
  1676. {/* First Search Box - Item Search with vertical layout */}
  1677. <Box sx={{ mt: 3, mb: 2 }}>
  1678. <Typography variant="h6" display="block" marginBlockEnd={1}>
  1679. {t("Search Items")}
  1680. </Typography>
  1681. <VerticalSearchBox
  1682. criteria={pickOrderSearchCriteria}
  1683. onSearch={handleSecondSearch}
  1684. onReset={handleSecondReset}
  1685. />
  1686. </Box>
  1687. {/* Create Group Section - 简化版本,不需要表单 */}
  1688. <Box sx={{ mt: 3, mb: 2 }}>
  1689. <Grid container spacing={2} alignItems="center">
  1690. <Grid item>
  1691. <Button
  1692. variant="outlined"
  1693. onClick={handleCreateGroup}
  1694. >
  1695. {t("Create New Group")}
  1696. </Button>
  1697. </Grid>
  1698. {groups.length > 0 && (
  1699. <>
  1700. <Grid item>
  1701. <Typography variant="body2">{t("Group")}:</Typography>
  1702. </Grid>
  1703. <Grid item>
  1704. <FormControl size="small" sx={{ minWidth: 200 }}>
  1705. <Select
  1706. value={selectedGroup?.id?.toString() || ""}
  1707. onChange={(e) => handleGroupChange(e.target.value)}
  1708. >
  1709. {groups.map((group) => (
  1710. <MenuItem key={group.id} value={group.id.toString()}>
  1711. {group.name}
  1712. </MenuItem>
  1713. ))}
  1714. </Select>
  1715. </FormControl>
  1716. </Grid>
  1717. {selectedGroup && (
  1718. <Grid item>
  1719. <LocalizationProvider dateAdapter={AdapterDayjs} adapterLocale="zh-hk">
  1720. <DatePicker
  1721. value={selectedGroup.targetDate && selectedGroup.targetDate !== "" ? dayjs(selectedGroup.targetDate) : null}
  1722. onChange={(date) => {
  1723. if (date) {
  1724. const formattedDate = date.format(INPUT_DATE_FORMAT);
  1725. handleGroupTargetDateChange(selectedGroup.id, formattedDate);
  1726. }
  1727. }}
  1728. slotProps={{
  1729. textField: {
  1730. size: "small",
  1731. label: t("Target Date"),
  1732. sx: { width: 180 }
  1733. },
  1734. }}
  1735. />
  1736. </LocalizationProvider>
  1737. </Grid>
  1738. )}
  1739. </>
  1740. )}
  1741. </Grid>
  1742. {/* Add group range text */}
  1743. {groups.length > 0 && (
  1744. <Box sx={{ mt: 1 }}>
  1745. <Typography variant="body2" color="text.secondary" sx={{ fontStyle: 'italic' }}>
  1746. {getGroupRangeText()}
  1747. </Typography>
  1748. </Box>
  1749. )}
  1750. </Box>
  1751. {/* Second Search Results - Use custom table like AssignAndRelease */}
  1752. {hasSearchedSecond && (
  1753. <Box sx={{ mt: 3 }}>
  1754. <Typography variant="h6" marginBlockEnd={2}>
  1755. {t("Search Results")} ({secondSearchResults.length})
  1756. </Typography>
  1757. {selectedSecondSearchItemIds.length > 0 && (
  1758. <Box sx={{ mb: 2 }}>
  1759. <Typography variant="body2" color="text.secondary" sx={{ fontStyle: 'italic' }}>
  1760. {t("Selected items will join above created group")}
  1761. </Typography>
  1762. </Box>
  1763. )}
  1764. {isLoadingSecondSearch ? (
  1765. <Typography>{t("Loading...")}</Typography>
  1766. ) : secondSearchResults.length === 0 ? (
  1767. <Typography color="textSecondary">{t("No results found")}</Typography>
  1768. ) : (
  1769. <SearchResultsTable
  1770. items={secondSearchResults}
  1771. selectedItemIds={selectedSecondSearchItemIds}
  1772. groups={groups}
  1773. onItemSelect={handleIndividualCheckboxChange}
  1774. onQtyChange={handleSecondSearchQtyChange}
  1775. onGroupChange={handleCreatedItemGroupChange}
  1776. onQtyBlur={handleQtyBlur}
  1777. isItemInCreated={isItemInCreated}
  1778. pageNum={searchResultsPagingController.pageNum}
  1779. pageSize={searchResultsPagingController.pageSize}
  1780. onPageChange={handleSearchResultsPageChange}
  1781. onPageSizeChange={handleSearchResultsPageSizeChange}
  1782. />
  1783. )}
  1784. </Box>
  1785. )}
  1786. {/* Add Submit Button between tables */}
  1787. {/*
  1788. {hasSearchedSecond && secondSearchResults.length > 0 && selectedSecondSearchItemIds.length > 0 && (
  1789. <Box sx={{ mt: 2, mb: 2 }}>
  1790. <Box sx={{ display: 'flex', justifyContent: 'left', alignItems: 'center', gap: 2 }}>
  1791. <Button
  1792. variant="contained"
  1793. color="primary"
  1794. onClick={handleAddSelectedToCreatedItems}
  1795. disabled={selectedSecondSearchItemIds.length === 0 || !areSelectedItemsValid()}
  1796. sx={{ minWidth: 200 }}
  1797. >
  1798. {t("Add Selected Items to Created Items")} ({selectedSecondSearchItemIds.length})
  1799. </Button>
  1800. {selectedSecondSearchItemIds.length > 0 && !areSelectedItemsValid() && (
  1801. <Typography
  1802. variant="body2"
  1803. color="error.main"
  1804. sx={{ fontStyle: 'italic' }}
  1805. >
  1806. {getValidationMessage()}
  1807. </Typography>
  1808. )}
  1809. </Box>
  1810. </Box>
  1811. )}
  1812. */}
  1813. {/* 创建项目区域 - 修改Group列为可选择的 */}
  1814. {createdItems.length > 0 && (
  1815. <Box sx={{ mt: 3 }}>
  1816. <Typography variant="h6" marginBlockEnd={2}>
  1817. {t("Created Items")} ({createdItems.length})
  1818. </Typography>
  1819. <CreatedItemsTable
  1820. items={createdItems}
  1821. groups={groups}
  1822. onItemSelect={handleCreatedItemSelect}
  1823. onQtyChange={handleQtyChange}
  1824. onGroupChange={handleCreatedItemGroupChange}
  1825. pageNum={createdItemsPagingController.pageNum}
  1826. pageSize={createdItemsPagingController.pageSize}
  1827. onPageChange={handleCreatedItemsPageChange}
  1828. onPageSizeChange={handleCreatedItemsPageSizeChange}
  1829. />
  1830. </Box>
  1831. )}
  1832. {/* 操作按钮 */}
  1833. <Stack direction="row" justifyContent="flex-start" gap={1} sx={{ mt: 3 }}>
  1834. <Button
  1835. name="submit"
  1836. variant="contained"
  1837. startIcon={<Check />}
  1838. type="submit"
  1839. disabled={createdItems.filter(item => item.isSelected).length === 0}
  1840. >
  1841. {t("Create Pick Order")}
  1842. </Button>
  1843. <Button
  1844. name="reset"
  1845. variant="outlined"
  1846. onClick={handleReset}
  1847. >
  1848. {t("reset")}
  1849. </Button>
  1850. </Stack>
  1851. </Box>
  1852. </FormProvider>
  1853. );
  1854. };
  1855. export default NewCreateItem;