|
- "use client";
-
- import { createPickOrder, SavePickOrderRequest, SavePickOrderLineRequest, getLatestGroupNameAndCreate, createOrUpdateGroups } from "@/app/api/pickOrder/actions";
- import {
- Autocomplete,
- Box,
- Button,
- FormControl,
- Grid,
- Stack,
- TextField,
- Typography,
- Checkbox,
- Table,
- TableBody,
- TableCell,
- TableContainer,
- TableHead,
- TableRow,
- Paper,
- Select,
- MenuItem,
- Modal,
- Card,
- CardContent,
- TablePagination,
- } from "@mui/material";
- import { Controller, FormProvider, SubmitHandler, useForm } from "react-hook-form";
- import { useTranslation } from "react-i18next";
- import { useCallback, useEffect, useMemo, useState } from "react";
- import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers";
- import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs";
- import dayjs from "dayjs";
- import { Check, Search, RestartAlt } from "@mui/icons-material";
- import { ItemCombo, fetchAllItemsInClient } from "@/app/api/settings/item/actions";
- import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil";
- import SearchResults, { Column } from "../SearchResults/SearchResults";
- import { fetchJobOrderDetailByCode } from "@/app/api/jo/actions";
- import SearchBox, { Criterion } from "../SearchBox";
- import VerticalSearchBox from "./VerticalSearchBox";
- import SearchResultsTable from './SearchResultsTable';
- import CreatedItemsTable from './CreatedItemsTable';
- type Props = {
- filterArgs?: Record<string, any>;
- searchQuery?: Record<string, any>;
- onPickOrderCreated?: () => void; // 添加回调函数
- };
-
- // 扩展表单类型以包含搜索字段
- interface SearchFormData extends SavePickOrderRequest {
- searchCode?: string;
- searchName?: string;
- }
-
- // Update the CreatedItem interface to allow null values for groupId
- interface CreatedItem {
- itemId: number;
- itemName: string;
- itemCode: string;
- qty: number;
- uom: string;
- uomId: number;
- uomDesc: string;
- isSelected: boolean;
- currentStockBalance?: number;
- targetDate?: string | null; // Make it optional to match the source
- groupId?: number | null; // Allow null values
- }
-
- // Add interface for search items with quantity
- interface SearchItemWithQty extends ItemCombo {
- qty: number | null; // Changed from number to number | null
- jobOrderCode?: string;
- jobOrderId?: number;
- currentStockBalance?: number;
- targetDate?: string | null; // Allow null values
- groupId?: number | null; // Allow null values
- }
- interface JobOrderDetailPickLine {
- id: number;
- code: string;
- name: string;
- lotNo: string | null;
- reqQty: number;
- uom: string;
- status: string;
- }
-
- // 添加组相关的接口
- interface Group {
- id: number;
- name: string;
- targetDate: string ;
- }
- // Move the counter outside the component to persist across re-renders
- let checkboxChangeCallCount = 0;
- let processingItems = new Set<number>();
-
- const NewCreateItem: React.FC<Props> = ({ filterArgs, searchQuery, onPickOrderCreated }) => {
- const { t } = useTranslation("pickOrder");
- const [items, setItems] = useState<ItemCombo[]>([]);
- const [filteredItems, setFilteredItems] = useState<SearchItemWithQty[]>([]);
- const [createdItems, setCreatedItems] = useState<CreatedItem[]>([]);
- const [isLoading, setIsLoading] = useState(false);
- const [hasSearched, setHasSearched] = useState(false);
-
- // 添加组相关的状态 - 只声明一次
- const [groups, setGroups] = useState<Group[]>([]);
- const [selectedGroup, setSelectedGroup] = useState<Group | null>(null);
- const [nextGroupNumber, setNextGroupNumber] = useState(1);
-
- // Add state for selected item IDs in search results
- const [selectedSearchItemIds, setSelectedSearchItemIds] = useState<(string | number)[]>([]);
-
- // Add state for second search
- const [secondSearchQuery, setSecondSearchQuery] = useState<Record<string, any>>({});
- const [secondSearchResults, setSecondSearchResults] = useState<SearchItemWithQty[]>([]);
- const [isLoadingSecondSearch, setIsLoadingSecondSearch] = useState(false);
- const [hasSearchedSecond, setHasSearchedSecond] = useState(false);
-
- // Add selection state for second search
- const [selectedSecondSearchItemIds, setSelectedSecondSearchItemIds] = useState<(string | number)[]>([]);
-
- const formProps = useForm<SearchFormData>();
- const errors = formProps.formState.errors;
- const targetDate = formProps.watch("targetDate");
- const type = formProps.watch("type");
- const searchCode = formProps.watch("searchCode");
- const searchName = formProps.watch("searchName");
- const [jobOrderItems, setJobOrderItems] = useState<JobOrderDetailPickLine[]>([]);
- const [isLoadingJobOrder, setIsLoadingJobOrder] = useState(false);
-
- useEffect(() => {
- const loadItems = async () => {
- try {
- const itemsData = await fetchAllItemsInClient();
- console.log("Loaded items:", itemsData);
- setItems(itemsData);
- setFilteredItems([]);
- } catch (error) {
- console.error("Error loading items:", error);
- }
- };
-
- loadItems();
- }, []);
- const searchJobOrderItems = useCallback(async (jobOrderCode: string) => {
- if (!jobOrderCode.trim()) return;
-
- setIsLoadingJobOrder(true);
- try {
- const jobOrderDetail = await fetchJobOrderDetailByCode(jobOrderCode);
- setJobOrderItems(jobOrderDetail.pickLines || []);
-
- // Fix the Job Order conversion - add missing uomDesc
- const convertedItems = (jobOrderDetail.pickLines || []).map(item => ({
- id: item.id,
- label: item.name,
- qty: item.reqQty,
- uom: item.uom,
- uomId: 0,
- uomDesc: item.uomDesc, // Add missing uomDesc
- jobOrderCode: jobOrderDetail.code,
- jobOrderId: jobOrderDetail.id,
- }));
-
- setFilteredItems(convertedItems);
- setHasSearched(true);
- } catch (error) {
- console.error("Error fetching Job Order items:", error);
- alert(t("Job Order not found or has no items"));
- } finally {
- setIsLoadingJobOrder(false);
- }
- }, [t]);
-
- // Update useEffect to handle Job Order search
- useEffect(() => {
- if (searchQuery && searchQuery.jobOrderCode) {
- searchJobOrderItems(searchQuery.jobOrderCode);
- } else if (searchQuery && items.length > 0) {
- // Existing item search logic
- // ... your existing search logic
- }
- }, [searchQuery, items, searchJobOrderItems]);
- useEffect(() => {
- if (searchQuery) {
- if (searchQuery.type) {
- formProps.setValue("type", searchQuery.type);
- }
-
- if (searchQuery.targetDate) {
- formProps.setValue("targetDate", searchQuery.targetDate);
- }
-
- if (searchQuery.code) {
- formProps.setValue("searchCode", searchQuery.code);
- }
-
- if (searchQuery.items) {
- formProps.setValue("searchName", searchQuery.items);
- }
- }
- }, [searchQuery, formProps]);
-
- useEffect(() => {
- setFilteredItems([]);
- setHasSearched(false);
- }, []);
-
- const typeList = [
- { type: "Consumable" },
- { type: "Material" },
- { type: "Product" }
- ];
-
- const handleTypeChange = useCallback(
- (event: React.SyntheticEvent, newValue: {type: string} | null) => {
- formProps.setValue("type", newValue?.type || "");
- },
- [formProps],
- );
-
- const handleSearch = useCallback(() => {
-
- if (!searchCode && !searchName) {
- alert(t("Please enter at least code or name"));
- return;
- }
-
- setIsLoading(true);
- setHasSearched(true);
-
- console.log("Searching with:", { type, searchCode, searchName, targetDate, itemsCount: items.length });
-
- setTimeout(() => {
- let filtered = items;
-
- if (searchCode && searchCode.trim()) {
- filtered = filtered.filter(item =>
- item.label.toLowerCase().includes(searchCode.toLowerCase())
- );
- console.log("After code filter:", filtered.length);
- }
-
- if (searchName && searchName.trim()) {
- filtered = filtered.filter(item =>
- item.label.toLowerCase().includes(searchName.toLowerCase())
- );
- console.log("After name filter:", filtered.length);
- }
-
- // Convert to SearchItemWithQty with default qty = null and include targetDate
- const filteredWithQty = filtered.slice(0, 100).map(item => ({
- ...item,
- qty: null,
- targetDate: targetDate, // Add target date to each item
- }));
- console.log("Final filtered results:", filteredWithQty.length);
- setFilteredItems(filteredWithQty);
- setIsLoading(false);
- }, 500);
- }, [type, searchCode, searchName, targetDate, items, t]); // Add targetDate back to dependencies
-
- // Handle quantity change in search results
- const handleSearchQtyChange = useCallback((itemId: number, newQty: number | null) => {
- setFilteredItems(prev =>
- prev.map(item =>
- item.id === itemId ? { ...item, qty: newQty } : item
- )
- );
-
- // Auto-update created items if this item exists there
- setCreatedItems(prev =>
- prev.map(item =>
- item.itemId === itemId ? { ...item, qty: newQty || 1 } : item
- )
- );
- }, []);
-
- // Modified handler for search item selection
- const handleSearchItemSelect = useCallback((itemId: number, isSelected: boolean) => {
- if (isSelected) {
- const item = filteredItems.find(i => i.id === itemId);
- if (!item) return;
-
- const existingItem = createdItems.find(created => created.itemId === item.id);
- if (existingItem) {
- alert(t("Item already exists in created items"));
- return;
- }
-
- // Fix the newCreatedItem creation - add missing uomDesc
- const newCreatedItem: CreatedItem = {
- itemId: item.id,
- itemName: item.label,
- itemCode: item.label,
- qty: item.qty || 1,
- uom: item.uom || "",
- uomId: item.uomId || 0,
- uomDesc: item.uomDesc || "", // Add missing uomDesc
- isSelected: true,
- currentStockBalance: item.currentStockBalance,
- targetDate: item.targetDate || targetDate, // Use item's targetDate or fallback to form's targetDate
- groupId: item.groupId || undefined, // Handle null values
- };
- setCreatedItems(prev => [...prev, newCreatedItem]);
- }
- }, [filteredItems, createdItems, t, targetDate]);
-
- // Handler for created item selection
- const handleCreatedItemSelect = useCallback((itemId: number, isSelected: boolean) => {
- setCreatedItems(prev =>
- prev.map(item =>
- item.itemId === itemId ? { ...item, isSelected } : item
- )
- );
- }, []);
-
- const handleQtyChange = useCallback((itemId: number, newQty: number) => {
- setCreatedItems(prev =>
- prev.map(item =>
- item.itemId === itemId ? { ...item, qty: newQty } : item
- )
- );
- }, []);
-
- // Check if item is already in created items
- const isItemInCreated = useCallback((itemId: number) => {
- return createdItems.some(item => item.itemId === itemId);
- }, [createdItems]);
-
- // 1) Created Items 行内改组:只改这一行的 groupId,并把该行 targetDate 同步为该组日期
- const handleCreatedItemGroupChange = useCallback((itemId: number, newGroupId: string) => {
- const gid = newGroupId ? Number(newGroupId) : undefined;
- const group = groups.find(g => g.id === gid);
- setCreatedItems(prev =>
- prev.map(it =>
- it.itemId === itemId
- ? {
- ...it,
- groupId: gid,
- targetDate: group?.targetDate || it.targetDate,
- }
- : it,
- ),
- );
- }, [groups]);
-
- // Update the handleGroupChange function to update target dates for items in the selected group
- const handleGroupChange = useCallback((groupId: string | number) => {
- const gid = typeof groupId === "string" ? Number(groupId) : groupId;
- const group = groups.find(g => g.id === gid);
- if (!group) return;
-
- setSelectedGroup(group);
-
- // Update target dates for items that belong to this group
- setSecondSearchResults(prev => prev.map(item =>
- item.groupId === gid
- ? {
- ...item,
- targetDate: group.targetDate
- }
- : item
- ));
- }, [groups]);
-
- // Update the handleGroupTargetDateChange function to update selected items that belong to that group
- const handleGroupTargetDateChange = useCallback((groupId: number, newTargetDate: string) => {
- setGroups(prev => prev.map(g => (g.id === groupId ? { ...g, targetDate: newTargetDate } : g)));
- setSelectedGroup(prev => {
- if (prev && prev.id === groupId) {
- return { ...prev, targetDate: newTargetDate };
- }
- return prev;
- });
- // Update selected items that belong to this group
- setSecondSearchResults(prev => prev.map(item =>
- item.groupId === groupId
- ? {
- ...item,
- targetDate: newTargetDate
- }
- : item
- ));
- setCreatedItems(prev => prev.map(item =>
- item.groupId === groupId
- ? {
- ...item,
- targetDate: newTargetDate
- }
- : item
- ));
- }, []);
-
- // Fix the handleCreateGroup function to use the API properly
- const handleCreateGroup = useCallback(async () => {
- try {
- // Use the API to get latest group name and create it automatically
- const response = await getLatestGroupNameAndCreate();
-
- if (response.id && response.name) {
- const newGroup: Group = {
- id: response.id,
- name: response.name,
- targetDate: ""
- };
-
- setGroups(prev => [...prev, newGroup]);
- setSelectedGroup(newGroup);
-
- console.log(`Created new group: ${response.name}`);
- } else {
- alert(t('Failed to create group'));
- }
- } catch (error) {
- console.error('Error creating group:', error);
- alert(t('Failed to create group'));
- }
- }, [t]);
-
- const checkAndAutoAddItem = useCallback((itemId: number) => {
- const item = secondSearchResults.find(i => i.id === itemId);
- if (!item) return;
-
- // Check if item has ALL 3 conditions:
- // 1. Item is selected (checkbox checked)
- const isSelected = selectedSecondSearchItemIds.includes(itemId);
- // 2. Group is assigned
- const hasGroup = item.groupId !== undefined && item.groupId !== null;
- // 3. Quantity is entered
- const hasQty = item.qty !== null && item.qty !== undefined && item.qty > 0;
-
- if (isSelected && hasGroup && hasQty && !isItemInCreated(item.id)) {
- // Auto-add to created items
- const newCreatedItem: CreatedItem = {
- itemId: item.id,
- itemName: item.label,
- itemCode: item.label,
- qty: item.qty || 1,
- uom: item.uom || "",
- uomId: item.uomId || 0,
- uomDesc: item.uomDesc || "",
- isSelected: true,
- currentStockBalance: item.currentStockBalance,
- targetDate: item.targetDate || targetDate,
- groupId: item.groupId || undefined,
- };
- setCreatedItems(prev => [...prev, newCreatedItem]);
-
- // Remove from search results since it's now in created items
- setSecondSearchResults(prev => prev.filter(searchItem => searchItem.id !== itemId));
- setSelectedSecondSearchItemIds(prev => prev.filter(id => id !== itemId));
- }
- }, [secondSearchResults, selectedSecondSearchItemIds, isItemInCreated, targetDate]);
- // Add this function after checkAndAutoAddItem
- // Add this function after checkAndAutoAddItem
- const handleQtyBlur = useCallback((itemId: number) => {
- // Only auto-add if item is already selected (scenario 1: select first, then enter quantity)
- setTimeout(() => {
- const currentItem = secondSearchResults.find(i => i.id === itemId);
- if (!currentItem) return;
-
- const isSelected = selectedSecondSearchItemIds.includes(itemId);
- const hasGroup = currentItem.groupId !== undefined && currentItem.groupId !== null;
- const hasQty = currentItem.qty !== null && currentItem.qty !== undefined && currentItem.qty > 0;
-
- // Only auto-add if item is already selected (scenario 1: select first, then enter quantity)
- if (isSelected && hasGroup && hasQty && !isItemInCreated(currentItem.id)) {
- const newCreatedItem: CreatedItem = {
- itemId: currentItem.id,
- itemName: currentItem.label,
- itemCode: currentItem.label,
- qty: currentItem.qty || 1,
- uom: currentItem.uom || "",
- uomId: currentItem.uomId || 0,
- uomDesc: currentItem.uomDesc || "",
- isSelected: true,
- currentStockBalance: currentItem.currentStockBalance,
- targetDate: currentItem.targetDate || targetDate,
- groupId: currentItem.groupId || undefined,
- };
- setCreatedItems(prev => [...prev, newCreatedItem]);
-
- setSecondSearchResults(prev => prev.filter(searchItem => searchItem.id !== itemId));
- setSelectedSecondSearchItemIds(prev => prev.filter(id => id !== itemId));
- }
- }, 0);
- }, [secondSearchResults, selectedSecondSearchItemIds, isItemInCreated, targetDate]);
- const handleSearchItemGroupChange = useCallback((itemId: number, groupId: string) => {
- const gid = groupId ? Number(groupId) : undefined;
- const group = groups.find(g => g.id === gid);
-
- setSecondSearchResults(prev => prev.map(item =>
- item.id === itemId
- ? {
- ...item,
- groupId: gid,
- targetDate: group?.targetDate || undefined
- }
- : item
- ));
-
- // Check auto-add after group assignment
- setTimeout(() => {
- checkAndAutoAddItem(itemId);
- }, 0);
- }, [groups, checkAndAutoAddItem]);
- // 5) 选中新增的待选项:依然按“当前 Group”赋 groupId + targetDate(新加入的应随 Group)
- const handleSecondSearchItemSelect = useCallback((itemId: number, isSelected: boolean) => {
- if (!isSelected) return;
- const item = secondSearchResults.find(i => i.id === itemId);
- if (!item) return;
- const exists = createdItems.find(c => c.itemId === item.id);
- if (exists) { alert(t("Item already exists in created items")); return; }
-
- // 找到项目所属的组,使用该组的 targetDate
- const itemGroup = groups.find(g => g.id === item.groupId);
- const itemTargetDate = itemGroup?.targetDate || item.targetDate || targetDate;
-
- const newCreatedItem: CreatedItem = {
- itemId: item.id,
- itemName: item.label,
- itemCode: item.label,
- qty: item.qty || 1,
- uom: item.uom || "",
- uomId: item.uomId || 0,
- uomDesc: item.uomDesc || "",
- isSelected: true,
- currentStockBalance: item.currentStockBalance,
- targetDate: itemTargetDate, // 使用项目所属组的 targetDate
- groupId: item.groupId || undefined, // 使用项目自身的 groupId
- };
- setCreatedItems(prev => [...prev, newCreatedItem]);
- }, [secondSearchResults, createdItems, groups, targetDate, t]);
-
- // 修改提交函数,按组分别创建提料单
- const onSubmit = useCallback<SubmitHandler<SearchFormData>>(
- async (data, event) => {
-
- const selectedCreatedItems = createdItems.filter(item => item.isSelected);
-
- if (selectedCreatedItems.length === 0) {
- alert(t("Please select at least one item to submit"));
- return;
- }
-
- // ✅ 修复:自动填充 type 为 "Consumable",不再强制用户选择
- // if (!data.type) {
- // alert(t("Please select product type"));
- // return;
- // }
-
- // 按组分组选中的项目
- const itemsByGroup = selectedCreatedItems.reduce((acc, item) => {
- const groupId = item.groupId || 'no-group';
- if (!acc[groupId]) {
- acc[groupId] = [];
- }
- acc[groupId].push(item);
- return acc;
- }, {} as Record<string | number, typeof selectedCreatedItems>);
-
- console.log("Items grouped by group:", itemsByGroup);
-
- let successCount = 0;
- const totalGroups = Object.keys(itemsByGroup).length;
- const groupUpdates: Array<{groupId: number, pickOrderId: number}> = [];
-
- // 为每个组创建提料单
- for (const [groupId, items] of Object.entries(itemsByGroup)) {
- try {
- // 获取组的名称和目标日期
- const group = groups.find(g => g.id === Number(groupId));
- const groupName = group?.name || 'No Group';
-
- // Use the group's target date, fallback to item's target date, then form's target date
- let groupTargetDate = group?.targetDate;
- if (!groupTargetDate && items.length > 0) {
- groupTargetDate = items[0].targetDate || undefined; // Add || undefined to handle null
- }
- if (!groupTargetDate) {
- groupTargetDate = data.targetDate;
- }
-
- // If still no target date, use today
- if (!groupTargetDate) {
- groupTargetDate = dayjs().format(INPUT_DATE_FORMAT);
- }
-
- console.log(`Creating pick order for group: ${groupName} with ${items.length} items, target date: ${groupTargetDate}`);
-
- let formattedTargetDate = groupTargetDate;
- if (groupTargetDate && typeof groupTargetDate === 'string') {
- try {
- const date = dayjs(groupTargetDate);
- formattedTargetDate = date.format('YYYY-MM-DD');
- } catch (error) {
- console.error("Invalid date format:", groupTargetDate);
- alert(t("Invalid date format"));
- return;
- }
- }
-
- // ✅ 修复:自动使用 "Consumable" 作为默认 type
- const pickOrderData: SavePickOrderRequest = {
- type: data.type || "Consumable", // 如果用户选择了 type 就用用户的,否则默认 "Consumable"
- targetDate: formattedTargetDate,
- pickOrderLine: items.map(item => ({
- itemId: item.itemId,
- qty: item.qty,
- uomId: item.uomId
- } as SavePickOrderLineRequest))
- };
-
- console.log(`Submitting pick order for group ${groupName}:`, pickOrderData);
-
- const res = await createPickOrder(pickOrderData);
- if (res.id) {
- console.log(`Pick order created successfully for group ${groupName}:`, res);
- successCount++;
-
- // Store group ID and pick order ID for updating
- if (groupId !== 'no-group' && group?.id) {
- groupUpdates.push({
- groupId: group.id,
- pickOrderId: res.id
- });
- }
- } else {
- console.error(`Failed to create pick order for group ${groupName}:`, res);
- alert(t(`Failed to create pick order for group ${groupName}`));
- return;
- }
- } catch (error) {
- console.error(`Error creating pick order for group ${groupId}:`, error);
- alert(t(`Error creating pick order for group ${groupId}`));
- return;
- }
- }
-
- // Update groups with pick order information
- if (groupUpdates.length > 0) {
- try {
- // Update each group with its corresponding pick order ID
- for (const update of groupUpdates) {
- const updateResponse = await createOrUpdateGroups({
- groupIds: [update.groupId],
- targetDate: data.targetDate,
- pickOrderId: update.pickOrderId
- });
-
- console.log(`Group ${update.groupId} updated with pick order ${update.pickOrderId}:`, updateResponse);
- }
- } catch (error) {
- console.error('Error updating groups:', error);
- // Don't fail the whole operation if group update fails
- }
- }
-
- // 所有组都创建成功后,清理选中的项目并切换到 Assign & Release
- if (successCount === totalGroups) {
- setCreatedItems(prev => prev.filter(item => !item.isSelected));
- formProps.reset();
- setHasSearched(false);
- setFilteredItems([]);
- alert(t("All pick orders created successfully"));
-
- // 通知父组件切换到 Assign & Release 标签页
- if (onPickOrderCreated) {
- onPickOrderCreated();
- }
- }
- },
- [createdItems, t, formProps, groups, onPickOrderCreated]
- );
-
- // Fix the handleReset function to properly clear all states including search results
- const handleReset = useCallback(() => {
- formProps.reset();
- setCreatedItems([]);
- setHasSearched(false);
- setFilteredItems([]);
-
- // Clear second search states completely
- setSecondSearchResults([]);
- setHasSearchedSecond(false);
- setSelectedSecondSearchItemIds([]);
- setSecondSearchQuery({});
-
- // Clear groups
- setGroups([]);
- setSelectedGroup(null);
- setNextGroupNumber(1);
-
- // Clear pagination states
- setSearchResultsPagingController({
- pageNum: 1,
- pageSize: 10,
- });
- setCreatedItemsPagingController({
- pageNum: 1,
- pageSize: 10,
- });
-
- // Clear first search states
- setSelectedSearchItemIds([]);
- }, [formProps]);
-
- // Pagination state
- const [page, setPage] = useState(0);
- const [rowsPerPage, setRowsPerPage] = useState(10);
-
- // Handle page change
- const handleChangePage = (
- _event: React.MouseEvent | React.KeyboardEvent,
- newPage: number,
- ) => {
- console.log(_event);
- setPage(newPage);
- // The original code had setPagingController and defaultPagingController,
- // but these are not defined in the provided context.
- // Assuming they are meant to be part of a larger context or will be added.
- // For now, commenting out the setPagingController part as it's not defined.
- // if (setPagingController) {
- // setPagingController({
- // ...(pagingController ?? defaultPagingController),
- // pageNum: newPage + 1,
- // });
- // }
- };
-
- // Handle rows per page change
- const handleChangeRowsPerPage = (
- event: React.ChangeEvent<HTMLInputElement>,
- ) => {
- console.log(event);
- setRowsPerPage(+event.target.value);
- setPage(0);
- // The original code had setPagingController and defaultPagingController,
- // but these are not defined in the provided context.
- // Assuming they are meant to be part of a larger context or will be added.
- // For now, commenting out the setPagingController part as it's not defined.
- // if (setPagingController) {
- // setPagingController({
- // ...(pagingController ?? defaultPagingController),
- // pageNum: 1,
- // });
- // }
- };
-
- // Add missing handleSearchCheckboxChange function
- const handleSearchCheckboxChange = useCallback((ids: (string | number)[] | ((prev: (string | number)[]) => (string | number)[])) => {
- if (typeof ids === 'function') {
- const newIds = ids(selectedSearchItemIds);
- setSelectedSearchItemIds(newIds);
-
- if (newIds.length === filteredItems.length) {
- // Select all
- filteredItems.forEach(item => {
- if (!isItemInCreated(item.id)) {
- handleSearchItemSelect(item.id, true);
- }
- });
- } else {
- // Handle individual selections
- filteredItems.forEach(item => {
- const isSelected = newIds.includes(item.id);
- const isCurrentlyInCreated = isItemInCreated(item.id);
-
- if (isSelected && !isCurrentlyInCreated) {
- handleSearchItemSelect(item.id, true);
- } else if (!isSelected && isCurrentlyInCreated) {
- setCreatedItems(prev => prev.filter(createdItem => createdItem.itemId !== item.id));
- }
- });
- }
- } else {
- const previousIds = selectedSearchItemIds;
- setSelectedSearchItemIds(ids);
-
- const newlySelected = ids.filter(id => !previousIds.includes(id));
- const newlyDeselected = previousIds.filter(id => !ids.includes(id));
-
- newlySelected.forEach(id => {
- if (!isItemInCreated(id as number)) {
- handleSearchItemSelect(id as number, true);
- }
- });
-
- newlyDeselected.forEach(id => {
- setCreatedItems(prev => prev.filter(createdItem => createdItem.itemId !== id));
- });
- }
- }, [selectedSearchItemIds, filteredItems, isItemInCreated, handleSearchItemSelect]);
-
- // Add pagination state for created items
- const [createdItemsPagingController, setCreatedItemsPagingController] = useState({
- pageNum: 1,
- pageSize: 10,
- });
-
- // Add pagination handlers for created items
- const handleCreatedItemsPageChange = useCallback((event: unknown, newPage: number) => {
- const newPagingController = {
- ...createdItemsPagingController,
- pageNum: newPage + 1,
- };
- setCreatedItemsPagingController(newPagingController);
- }, [createdItemsPagingController]);
-
- const handleCreatedItemsPageSizeChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
- const newPageSize = parseInt(event.target.value, 10);
- const newPagingController = {
- pageNum: 1,
- pageSize: newPageSize,
- };
- setCreatedItemsPagingController(newPagingController);
- }, []);
-
- // Create a custom table for created items with pagination
- const CustomCreatedItemsTable = () => {
- const startIndex = (createdItemsPagingController.pageNum - 1) * createdItemsPagingController.pageSize;
- const endIndex = startIndex + createdItemsPagingController.pageSize;
- const paginatedCreatedItems = createdItems.slice(startIndex, endIndex);
-
- return (
- <>
- <TableContainer component={Paper}>
- <Table>
- <TableHead>
- <TableRow>
- <TableCell padding="checkbox" sx={{ width: '80px', minWidth: '80px' }}>
- {t("Selected")}
- </TableCell>
- <TableCell>
- {t("Item")}
- </TableCell>
- <TableCell>
- {t("Group")}
- </TableCell>
- <TableCell align="right">
- {t("Current Stock")}
- </TableCell>
- <TableCell align="right">
- {t("Stock Unit")}
- </TableCell>
- <TableCell align="right">
- {t("Order Quantity")}
- </TableCell>
- <TableCell align="right">
- {t("Target Date")}
- </TableCell>
- </TableRow>
- </TableHead>
- <TableBody>
- {paginatedCreatedItems.length === 0 ? (
- <TableRow>
- <TableCell colSpan={12} align="center">
- <Typography variant="body2" color="text.secondary">
- {t("No created items")}
- </Typography>
- </TableCell>
- </TableRow>
- ) : (
- paginatedCreatedItems.map((item) => (
- <TableRow key={item.itemId}>
- <TableCell padding="checkbox">
- <Checkbox
- checked={item.isSelected}
- onChange={(e) => handleCreatedItemSelect(item.itemId, e.target.checked)}
- />
- </TableCell>
- <TableCell>
- <Typography variant="body2">{item.itemName}</Typography>
- <Typography variant="caption" color="textSecondary">
- {item.itemCode}
- </Typography>
- </TableCell>
- <TableCell>
- <FormControl size="small" sx={{ minWidth: 120 }}>
- <Select
- value={item.groupId?.toString() || ""}
- onChange={(e) => handleCreatedItemGroupChange(item.itemId, e.target.value)}
- displayEmpty
- >
- <MenuItem value="">
- <em>{t("No Group")}</em>
- </MenuItem>
- {groups.map((group) => (
- <MenuItem key={group.id} value={group.id.toString()}>
- {group.name}
- </MenuItem>
- ))}
- </Select>
- </FormControl>
- </TableCell>
- <TableCell align="right">
- <Typography
- variant="body2"
- color={item.currentStockBalance && item.currentStockBalance > 0 ? "success.main" : "error.main"}
- >
- {item.currentStockBalance?.toLocaleString() || 0}
- </Typography>
- </TableCell>
- <TableCell align="right">
- <Typography variant="body2">{item.uomDesc}</Typography>
- </TableCell>
- <TableCell align="right">
- <TextField
- type="number"
- size="small"
- value={item.qty || ""}
- onChange={(e) => {
- const newQty = Number(e.target.value);
- handleQtyChange(item.itemId, newQty);
- }}
- inputProps={{
- min: 1,
- step: 1,
- style: { textAlign: 'center' }
- }}
- sx={{
- width: '80px',
- '& .MuiInputBase-input': {
- textAlign: 'center',
- cursor: 'text'
- }
- }}
- />
- </TableCell>
- <TableCell align="right">
- <Typography variant="body2">
- {item.targetDate&& item.targetDate !== "" ? new Date(item.targetDate).toLocaleDateString() : "-"}
- </Typography>
- </TableCell>
- </TableRow>
- ))
- )}
- </TableBody>
- </Table>
- </TableContainer>
-
- {/* Pagination for created items */}
- <TablePagination
- component="div"
- count={createdItems.length}
- page={(createdItemsPagingController.pageNum - 1)}
- rowsPerPage={createdItemsPagingController.pageSize}
- onPageChange={handleCreatedItemsPageChange}
- onRowsPerPageChange={handleCreatedItemsPageSizeChange}
- rowsPerPageOptions={[10, 25, 50]}
- labelRowsPerPage={t("Rows per page")}
- labelDisplayedRows={({ from, to, count }) =>
- `${from}-${to} of ${count !== -1 ? count : `more than ${to}`}`
- }
- />
- </>
- );
- };
-
- // Define columns for SearchResults
- const searchItemColumns: Column<SearchItemWithQty>[] = useMemo(() => [
- {
- name: "id",
- label: "",
- type: "checkbox",
- disabled: (item) => isItemInCreated(item.id), // Disable if already in created items
- },
-
- {
- name: "label",
- label: t("Item"),
- renderCell: (item) => {
-
- const parts = item.label.split(' - ');
- const code = parts[0] || '';
- const name = parts[1] || '';
-
- return (
- <Box>
- <Typography variant="body2">
- {name} {/* 显示项目名称 */}
- </Typography>
- <Typography variant="caption" color="textSecondary">
- {code} {/* 显示项目代码 */}
- </Typography>
- </Box>
- );
- },
- },
- {
- name: "qty",
- label: t("Order Quantity"),
- renderCell: (item) => (
- <TextField
- type="number"
- size="small"
- value={item.qty || ""} // Show empty string if qty is null
- onChange={(e) => {
- const value = e.target.value;
- const numValue = value === "" ? null : Number(value);
- handleSearchQtyChange(item.id, numValue);
- }}
- inputProps={{
- min: 1,
- step: 1,
- style: { textAlign: 'center' } // Center the text
- }}
- sx={{
- width: '80px',
- '& .MuiInputBase-input': {
- textAlign: 'center',
- cursor: 'text'
- }
- }}
- />
- ),
- },
- {
- name: "currentStockBalance",
- label: t("Current Stock"),
- renderCell: (item) => {
- const stockBalance = item.currentStockBalance || 0;
- return (
- <Typography
- variant="body2"
- color={stockBalance > 0 ? "success.main" : "error.main"}
- sx={{ fontWeight: stockBalance > 0 ? 'bold' : 'normal' }}
- >
- {stockBalance}
- </Typography>
- );
- },
- },
- {
- name: "targetDate",
- label: t("Target Date"),
- renderCell: (item) => (
- <Typography variant="body2">
- {item.targetDate ? new Date(item.targetDate).toLocaleDateString() : "-"}
- </Typography>
- ),
- },
- {
- name: "uom",
- label: t("Stock Unit"),
- renderCell: (item) => item.uom || "-",
- },
- ], [t, isItemInCreated, handleSearchQtyChange]);
- // 修改搜索条件为3行,每行一个 - 确保SearchBox组件能正确处理
- const pickOrderSearchCriteria: Criterion<any>[] = useMemo(
- () => [
-
-
- {
- label: t("Item Code"),
- paramName: "code",
- type: "text"
- },
- {
- label: t("Item Name"),
- paramName: "name",
- type: "text"
- },
- {
- label: t("Product Type"),
- paramName: "type",
- type: "autocomplete",
- options: [
- { value: "Consumable", label: t("Consumable") },
- { value: "MATERIAL", label: t("Material") },
- { value: "End_product", label: t("End Product") }
- ],
- },
- ],
- [t],
- );
-
- // 添加重置函数
- const handleSecondReset = useCallback(() => {
- console.log("Second search reset");
- setSecondSearchQuery({});
- setSecondSearchResults([]);
- setHasSearchedSecond(false);
- // 清空表单中的类型,但保留今天的日期
- formProps.setValue("type", "");
- const today = dayjs().format(INPUT_DATE_FORMAT);
- formProps.setValue("targetDate", today);
- }, [formProps]);
-
- // 添加数量变更处理函数
- const handleSecondSearchQtyChange = useCallback((itemId: number, newQty: number | null) => {
- setSecondSearchResults(prev =>
- prev.map(item =>
- item.id === itemId ? { ...item, qty: newQty } : item
- )
- );
-
- // Don't auto-add here - only on blur event
- }, []);
-
- // Add checkbox change handler for second search
- const handleSecondSearchCheckboxChange = useCallback((ids: (string | number)[] | ((prev: (string | number)[]) => (string | number)[])) => {
- if (typeof ids === 'function') {
- const newIds = ids(selectedSecondSearchItemIds);
- setSelectedSecondSearchItemIds(newIds);
-
- // 处理全选逻辑 - 选择所有搜索结果,不仅仅是当前页面
- if (newIds.length === secondSearchResults.length) {
- // 全选:将所有搜索结果添加到创建项目
- secondSearchResults.forEach(item => {
- if (!isItemInCreated(item.id)) {
- handleSearchItemSelect(item.id, true);
- }
- });
- } else {
- // 部分选择:只处理当前页面的选择
- secondSearchResults.forEach(item => {
- const isSelected = newIds.includes(item.id);
- const isCurrentlyInCreated = isItemInCreated(item.id);
-
- if (isSelected && !isCurrentlyInCreated) {
- handleSearchItemSelect(item.id, true);
- } else if (!isSelected && isCurrentlyInCreated) {
- setCreatedItems(prev => prev.filter(createdItem => createdItem.itemId !== item.id));
- }
- });
- }
- } else {
- const previousIds = selectedSecondSearchItemIds;
- setSelectedSecondSearchItemIds(ids);
-
- const newlySelected = ids.filter(id => !previousIds.includes(id));
- const newlyDeselected = previousIds.filter(id => !ids.includes(id));
-
- newlySelected.forEach(id => {
- if (!isItemInCreated(id as number)) {
- handleSearchItemSelect(id as number, true);
- }
- });
-
- newlyDeselected.forEach(id => {
- setCreatedItems(prev => prev.filter(createdItem => createdItem.itemId !== id));
- });
- }
- }, [selectedSecondSearchItemIds, secondSearchResults, isItemInCreated, handleSearchItemSelect]);
-
- // Update the secondSearchItemColumns to add right alignment for Current Stock and Order Quantity
- const secondSearchItemColumns: Column<SearchItemWithQty>[] = useMemo(() => [
- {
- name: "id",
- label: "",
- type: "checkbox",
- disabled: (item) => isItemInCreated(item.id),
- },
- {
- name: "label",
- label: t("Item"),
- renderCell: (item) => {
- const parts = item.label.split(' - ');
- const code = parts[0] || '';
- const name = parts[1] || '';
-
- return (
- <Box>
- <Typography variant="body2">
- {name}
- </Typography>
- <Typography variant="caption" color="textSecondary">
- {code}
- </Typography>
- </Box>
- );
- },
- },
- {
- name: "currentStockBalance",
- label: t("Current Stock"),
- align: "right", // Add right alignment for the label
- renderCell: (item) => {
- const stockBalance = item.currentStockBalance || 0;
- return (
- <Box sx={{ display: 'flex', justifyContent: 'flex-end', width: '100%' }}>
- <Typography
- variant="body2"
- color={stockBalance > 0 ? "success.main" : "error.main"}
- sx={{
- fontWeight: stockBalance > 0 ? 'bold' : 'normal',
- textAlign: 'right' // Add right alignment for the value
- }}
- >
- {stockBalance}
- </Typography>
- </Box>
- );
- },
- },
- {
- name: "uom",
- label: t("Stock Unit"),
- align: "right", // Add right alignment for the label
- renderCell: (item) => (
- <Box sx={{ display: 'flex', justifyContent: 'flex-end', width: '100%' }}>
- <Typography sx={{ textAlign: 'right' }}> {/* Add right alignment for the value */}
- {item.uom || "-"}
- </Typography>
- </Box>
- ),
- },
- {
- name: "qty",
- label: t("Order Quantity"),
- align: "right",
- renderCell: (item) => (
- <Box sx={{ display: 'flex', justifyContent: 'flex-end', width: '100%' }}>
- <TextField
- type="number"
- size="small"
- value={item.qty || ""}
- onChange={(e) => {
- const value = e.target.value;
- // Only allow numbers
- if (value === "" || /^\d+$/.test(value)) {
- const numValue = value === "" ? null : Number(value);
- handleSecondSearchQtyChange(item.id, numValue);
- }
- }}
- inputProps={{
- style: { textAlign: 'center' }
- }}
- sx={{
- width: '80px',
- '& .MuiInputBase-input': {
- textAlign: 'center',
- cursor: 'text'
- }
- }}
- onBlur={(e) => {
- const value = e.target.value;
- const numValue = value === "" ? null : Number(value);
- if (numValue !== null && numValue < 1) {
- handleSecondSearchQtyChange(item.id, 1); // Enforce min value
- }
- }}
- />
- </Box>
- ),
- }
- ], [t, isItemInCreated, handleSecondSearchQtyChange, groups]);
-
- // 添加缺失的 handleSecondSearch 函数
- const handleSecondSearch = useCallback((query: Record<string, any>) => {
- console.log("Second search triggered with query:", query);
- setSecondSearchQuery({ ...query });
- setIsLoadingSecondSearch(true);
-
- // Sync second search box info to form - ensure type value is correct
- if (query.type) {
- // Ensure type value matches backend enum format
- let correctType = query.type;
- if (query.type === "consumable") {
- correctType = "Consumable";
- } else if (query.type === "material") {
- correctType = "MATERIAL";
- } else if (query.type === "jo") {
- correctType = "JOB_ORDER";
- }
- formProps.setValue("type", correctType);
- }
-
- setTimeout(() => {
- let filtered = items;
-
- // Same filtering logic as first search
- if (query.code && query.code.trim()) {
- filtered = filtered.filter(item =>
- item.label.toLowerCase().includes(query.code.toLowerCase())
- );
- }
-
- if (query.name && query.name.trim()) {
- filtered = filtered.filter(item =>
- item.label.toLowerCase().includes(query.name.toLowerCase())
- );
- }
-
- if (query.type && query.type !== "All") {
- // Filter by type if needed
- }
-
- // Convert to SearchItemWithQty with NO group/targetDate initially
- const filteredWithQty = filtered.slice(0, 100).map(item => ({
- ...item,
- qty: null,
- targetDate: undefined, // No target date initially
- groupId: undefined, // No group initially
- }));
-
- setSecondSearchResults(filteredWithQty);
- setHasSearchedSecond(true);
- setIsLoadingSecondSearch(false);
- }, 500);
- }, [items, formProps]);
- /*
- // Create a custom search box component that displays fields vertically
- const VerticalSearchBox = ({ criteria, onSearch, onReset }: {
- criteria: Criterion<any>[];
- onSearch: (inputs: Record<string, any>) => void;
- onReset?: () => void;
- }) => {
- const { t } = useTranslation("common");
- const [inputs, setInputs] = useState<Record<string, any>>({});
-
- const handleInputChange = (paramName: string, value: any) => {
- setInputs(prev => ({ ...prev, [paramName]: value }));
- };
-
- const handleSearch = () => {
- onSearch(inputs);
- };
-
- const handleReset = () => {
- setInputs({});
- onReset?.();
- };
-
- return (
- <Card>
- <CardContent sx={{ display: "flex", flexDirection: "column", gap: 1 }}>
- <Typography variant="overline">{t("Search Criteria")}</Typography>
- <Grid container spacing={2} columns={{ xs: 12, sm: 12 }}>
- {criteria.map((c) => {
- return (
- <Grid key={c.paramName} item xs={12}>
- {c.type === "text" && (
- <TextField
- label={t(c.label)}
- fullWidth
- onChange={(e) => handleInputChange(c.paramName, e.target.value)}
- value={inputs[c.paramName] || ""}
- />
- )}
- {c.type === "autocomplete" && (
- <Autocomplete
- options={c.options || []}
- getOptionLabel={(option: any) => option.label}
- onChange={(_, value: any) => handleInputChange(c.paramName, value?.value || "")}
- value={c.options?.find(option => option.value === inputs[c.paramName]) || null}
- renderInput={(params) => (
- <TextField
- {...params}
- label={t(c.label)}
- fullWidth
- />
- )}
- />
- )}
- </Grid>
- );
- })}
- </Grid>
- <Stack direction="row" spacing={2} sx={{ mt: 2 }}>
- <Button
- variant="text"
- startIcon={<RestartAlt />}
- onClick={handleReset}
- >
- {t("Reset")}
- </Button>
- <Button
- variant="outlined"
- startIcon={<Search />}
- onClick={handleSearch}
- >
- {t("Search")}
- </Button>
- </Stack>
- </CardContent>
- </Card>
- );
- };
- */
- // Add pagination state for search results
- const [searchResultsPagingController, setSearchResultsPagingController] = useState({
- pageNum: 1,
- pageSize: 10,
- });
-
- // Add pagination handlers for search results
- const handleSearchResultsPageChange = useCallback((event: unknown, newPage: number) => {
- const newPagingController = {
- ...searchResultsPagingController,
- pageNum: newPage + 1, // API uses 1-based pagination
- };
- setSearchResultsPagingController(newPagingController);
- }, [searchResultsPagingController]);
-
- const handleSearchResultsPageSizeChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
- const newPageSize = parseInt(event.target.value, 10);
- const newPagingController = {
- pageNum: 1, // Reset to first page
- pageSize: newPageSize,
- };
- setSearchResultsPagingController(newPagingController);
- }, []);
- const getValidationMessage = useCallback(() => {
- const selectedItems = secondSearchResults.filter(item =>
- selectedSecondSearchItemIds.includes(item.id)
- );
-
- const itemsWithoutGroup = selectedItems.filter(item =>
- item.groupId === undefined || item.groupId === null
- );
-
- const itemsWithoutQty = selectedItems.filter(item =>
- item.qty === null || item.qty === undefined || item.qty <= 0
- );
-
- if (itemsWithoutGroup.length > 0 && itemsWithoutQty.length > 0) {
- return t("Please select group and enter quantity for all selected items");
- } else if (itemsWithoutGroup.length > 0) {
- return t("Please select group for all selected items");
- } else if (itemsWithoutQty.length > 0) {
- return t("Please enter quantity for all selected items");
- }
-
- return "";
- }, [secondSearchResults, selectedSecondSearchItemIds, t]);
- // Fix the handleAddSelectedToCreatedItems function to properly clear selections
- const handleAddSelectedToCreatedItems = useCallback(() => {
- const selectedItems = secondSearchResults.filter(item =>
- selectedSecondSearchItemIds.includes(item.id)
- );
-
- // Add selected items to created items with their own group info
- selectedItems.forEach(item => {
- if (!isItemInCreated(item.id)) {
- const newCreatedItem: CreatedItem = {
- itemId: item.id,
- itemName: item.label,
- itemCode: item.label,
- qty: item.qty || 1,
- uom: item.uom || "",
- uomId: item.uomId || 0,
- uomDesc: item.uomDesc || "",
- isSelected: true,
- currentStockBalance: item.currentStockBalance,
- targetDate: item.targetDate || targetDate,
- groupId: item.groupId || undefined,
- };
- setCreatedItems(prev => [...prev, newCreatedItem]);
- }
- });
-
- // Clear the selection
- setSelectedSecondSearchItemIds([]);
-
- // Remove the selected/added items from search results entirely
- setSecondSearchResults(prev => prev.filter(item =>
- !selectedSecondSearchItemIds.includes(item.id)
- ));
- }, [secondSearchResults, selectedSecondSearchItemIds, isItemInCreated, targetDate]);
-
- // Add a validation function to check if selected items are valid
- const areSelectedItemsValid = useCallback(() => {
- const selectedItems = secondSearchResults.filter(item =>
- selectedSecondSearchItemIds.includes(item.id)
- );
-
- return selectedItems.every(item =>
- item.groupId !== undefined &&
- item.groupId !== null &&
- item.qty !== null &&
- item.qty !== undefined &&
- item.qty > 0
- );
- }, [secondSearchResults, selectedSecondSearchItemIds]);
-
- // Move these handlers to the component level (outside of CustomSearchResultsTable)
- // Handle individual checkbox change - ONLY select, don't add to created items
- const handleIndividualCheckboxChange = useCallback((itemId: number, checked: boolean) => {
- checkboxChangeCallCount++;
-
- if (checked) {
- // Add to selected IDs
- setSelectedSecondSearchItemIds(prev => [...prev, itemId]);
-
- // Set the item's group and targetDate to current group when selected
- setSecondSearchResults(prev => {
- const updatedResults = prev.map(item =>
- item.id === itemId
- ? {
- ...item,
- groupId: selectedGroup?.id || undefined,
- targetDate: selectedGroup?.targetDate !== undefined && selectedGroup?.targetDate !== "" ? selectedGroup.targetDate : undefined
- }
- : item
- );
-
- // Check if should auto-add after state update
- setTimeout(() => {
- // Check if we're already processing this item
- if (processingItems.has(itemId)) {
- //alert(`Item ${itemId} is already being processed, skipping duplicate auto-add`);
- return;
- }
-
- const updatedItem = updatedResults.find(i => i.id === itemId);
- if (updatedItem) {
- const isSelected = true; // We just selected it
- const hasGroup = updatedItem.groupId !== undefined && updatedItem.groupId !== null;
- const hasQty = updatedItem.qty !== null && updatedItem.qty !== undefined && updatedItem.qty > 0;
-
- // Only auto-add if item has quantity (scenario 2: enter quantity first, then select)
- if (isSelected && hasGroup && hasQty && !isItemInCreated(updatedItem.id)) {
- // Mark this item as being processed
- processingItems.add(itemId);
-
- const newCreatedItem: CreatedItem = {
- itemId: updatedItem.id,
- itemName: updatedItem.label,
- itemCode: updatedItem.label,
- qty: updatedItem.qty || 1,
- uom: updatedItem.uom || "",
- uomId: updatedItem.uomId || 0,
- uomDesc: updatedItem.uomDesc || "",
- isSelected: true,
- currentStockBalance: updatedItem.currentStockBalance,
- targetDate: updatedItem.targetDate || targetDate,
- groupId: updatedItem.groupId || undefined,
- };
- setCreatedItems(prev => [...prev, newCreatedItem]);
-
- setSecondSearchResults(current => current.filter(searchItem => searchItem.id !== itemId));
- setSelectedSecondSearchItemIds(current => current.filter(id => id !== itemId));
-
- // Remove from processing set after a short delay
- setTimeout(() => {
- processingItems.delete(itemId);
- }, 100);
- }
-
- // Show final debug info in one alert
- /*
- alert(`FINAL DEBUG INFO for item ${itemId}:
- Function called ${checkboxChangeCallCount} times
- Is Selected: ${isSelected}
- Has Group: ${hasGroup}
- Has Quantity: ${hasQty}
- Quantity: ${updatedItem.qty}
- Group ID: ${updatedItem.groupId}
- Is Item In Created: ${isItemInCreated(updatedItem.id)}
- Auto-add triggered: ${isSelected && hasGroup && hasQty && !isItemInCreated(updatedItem.id)}
- Processing items: ${Array.from(processingItems).join(', ')}`);
- */
- }
- }, 0);
-
- return updatedResults;
- });
- } else {
- // Remove from selected IDs
- setSelectedSecondSearchItemIds(prev => prev.filter(id => id !== itemId));
-
- // Clear the item's group and targetDate when deselected
- setSecondSearchResults(prev => prev.map(item =>
- item.id === itemId
- ? {
- ...item,
- groupId: undefined,
- targetDate: undefined
- }
- : item
- ));
- }
- }, [selectedGroup, isItemInCreated, targetDate]);
-
- // Handle select all checkbox for current page
- const handleSelectAllOnPage = useCallback((checked: boolean, paginatedResults: SearchItemWithQty[]) => {
- if (checked) {
- // Select all items on current page that are not already in created items
- const newSelectedIds = paginatedResults
- .filter(item => !isItemInCreated(item.id))
- .map(item => item.id);
-
- setSelectedSecondSearchItemIds(prev => {
- const existingIds = prev.filter(id => !paginatedResults.some(item => item.id === id));
- return [...existingIds, ...newSelectedIds];
- });
-
- // Set group and targetDate for all selected items on current page
- setSecondSearchResults(prev => prev.map(item =>
- newSelectedIds.includes(item.id)
- ? {
- ...item,
- groupId: selectedGroup?.id || undefined,
- targetDate: selectedGroup?.targetDate !== undefined && selectedGroup.targetDate !== "" ? selectedGroup.targetDate : undefined
- }
- : item
- ));
- } else {
- // Deselect all items on current page
- const pageItemIds = paginatedResults.map(item => item.id);
- setSelectedSecondSearchItemIds(prev => prev.filter(id => !pageItemIds.includes(id as number)));
-
- // Clear group and targetDate for all deselected items on current page
- setSecondSearchResults(prev => prev.map(item =>
- pageItemIds.includes(item.id)
- ? {
- ...item,
- groupId: undefined,
- targetDate: undefined
- }
- : item
- ));
- }
- }, [selectedGroup, isItemInCreated]);
-
- // Update the CustomSearchResultsTable to use the handlers from component level
- /*
- const CustomSearchResultsTable = () => {
- // Calculate pagination
- const startIndex = (searchResultsPagingController.pageNum - 1) * searchResultsPagingController.pageSize;
- const endIndex = startIndex + searchResultsPagingController.pageSize;
- const paginatedResults = secondSearchResults.slice(startIndex, endIndex);
-
- // Check if all items on current page are selected
- const allSelectedOnPage = paginatedResults.length > 0 &&
- paginatedResults.every(item => selectedSecondSearchItemIds.includes(item.id));
-
- // Check if some items on current page are selected
- const someSelectedOnPage = paginatedResults.some(item => selectedSecondSearchItemIds.includes(item.id));
-
- return (
- <>
- <TableContainer component={Paper}>
- <Table>
- <TableHead>
- <TableRow>
- <TableCell padding="checkbox" sx={{ width: '80px', minWidth: '80px' }}>
- {t("Selected")}
- </TableCell>
- <TableCell>
- {t("Item")}
- </TableCell>
- <TableCell>
- {t("Group")}
- </TableCell>
- <TableCell align="right">
- {t("Current Stock")}
- </TableCell>
- <TableCell align="right">
- {t("Stock Unit")}
- </TableCell>
- <TableCell align="right">
- {t("Order Quantity")}
- </TableCell>
- <TableCell align="right">
- {t("Target Date")}
- </TableCell>
- </TableRow>
- </TableHead>
- <TableBody>
- {paginatedResults.length === 0 ? (
- <TableRow>
- <TableCell colSpan={12} align="center">
- <Typography variant="body2" color="text.secondary">
- {t("No data available")}
- </Typography>
- </TableCell>
- </TableRow>
- ) : (
- paginatedResults.map((item) => (
- <TableRow key={item.id}>
- <TableCell padding="checkbox">
- <Checkbox
- checked={selectedSecondSearchItemIds.includes(item.id)}
- onChange={(e) => handleIndividualCheckboxChange(item.id, e.target.checked)}
- disabled={isItemInCreated(item.id)}
- />
- </TableCell>
-
-
- <TableCell>
- <Box>
- <Typography variant="body2">
- {item.label.split(' - ')[1] || item.label}
- </Typography>
- <Typography variant="caption" color="textSecondary">
- {item.label.split(' - ')[0] || ''}
- </Typography>
- </Box>
- </TableCell>
-
-
- <TableCell>
- <Typography variant="body2">
- {(() => {
- if (item.groupId) {
- const group = groups.find(g => g.id === item.groupId);
- return group?.name || "-";
- }
- return "-"; // Show "-" for unselected items
- })()}
- </Typography>
- </TableCell>
-
-
- <TableCell align="right">
- <Typography
- variant="body2"
- color={item.currentStockBalance && item.currentStockBalance > 0 ? "success.main" : "error.main"}
- sx={{ fontWeight: item.currentStockBalance && item.currentStockBalance > 0 ? 'bold' : 'normal' }}
- >
- {item.currentStockBalance || 0}
- </Typography>
- </TableCell>
-
-
- <TableCell align="right">
- <Typography variant="body2">
- {item.uomDesc || "-"}
- </Typography>
- </TableCell>
-
- <TableCell align="right">
- <TextField
- type="number"
- size="small"
- value={item.qty || ""}
- onChange={(e) => {
- const value = e.target.value;
- // Only allow numbers
- if (value === "" || /^\d+$/.test(value)) {
- const numValue = value === "" ? null : Number(value);
- handleSecondSearchQtyChange(item.id, numValue);
- }
- }}
- onBlur={() => {
- // Trigger auto-add check when user finishes input
- handleQtyBlur(item.id);
- }}
- inputProps={{
- style: { textAlign: 'center' }
- }}
- sx={{
- width: '80px',
- '& .MuiInputBase-input': {
- textAlign: 'center',
- cursor: 'text'
- }
- }}
- />
- </TableCell>
-
-
- <TableCell align="right">
- <Typography variant="body2">
- {item.targetDate ? new Date(item.targetDate).toLocaleDateString() : "-"}
- </Typography>
- </TableCell>
- </TableRow>
- ))
- )}
- </TableBody>
- </Table>
- </TableContainer>
-
-
- <TablePagination
- component="div"
- count={secondSearchResults.length}
- page={(searchResultsPagingController.pageNum - 1)} // Convert to 0-based for TablePagination
- rowsPerPage={searchResultsPagingController.pageSize}
- onPageChange={handleSearchResultsPageChange}
- onRowsPerPageChange={handleSearchResultsPageSizeChange}
- rowsPerPageOptions={[10, 25, 50]}
- labelRowsPerPage={t("Rows per page")}
- labelDisplayedRows={({ from, to, count }) =>
- `${from}-${to} of ${count !== -1 ? count : `more than ${to}`}`
- }
- />
- </>
- );
- };
- */
-
- // Add helper function to get group range text
- const getGroupRangeText = useCallback(() => {
- if (groups.length === 0) return "";
-
- const firstGroup = groups[0];
- const lastGroup = groups[groups.length - 1];
-
- if (firstGroup.id === lastGroup.id) {
- return `${t("First created group")}: ${firstGroup.name}`;
- } else {
- return `${t("First created group")}: ${firstGroup.name} - ${t("Latest created group")}: ${lastGroup.name}`;
- }
- }, [groups, t]);
-
- return (
- <FormProvider {...formProps}>
- <Box
- component="form"
- onSubmit={formProps.handleSubmit(onSubmit)}
- >
- {/* First Search Box - Item Search with vertical layout */}
- <Box sx={{ mt: 3, mb: 2 }}>
- <Typography variant="h6" display="block" marginBlockEnd={1}>
- {t("Search Items")}
- </Typography>
-
- <VerticalSearchBox
- criteria={pickOrderSearchCriteria}
- onSearch={handleSecondSearch}
- onReset={handleSecondReset}
- />
- </Box>
-
- {/* Create Group Section - 简化版本,不需要表单 */}
- <Box sx={{ mt: 3, mb: 2 }}>
- <Grid container spacing={2} alignItems="center">
- <Grid item>
- <Button
- variant="outlined"
- onClick={handleCreateGroup}
- >
- {t("Create New Group")}
- </Button>
- </Grid>
-
- {groups.length > 0 && (
- <>
- <Grid item>
- <Typography variant="body2">{t("Group")}:</Typography>
- </Grid>
- <Grid item>
- <FormControl size="small" sx={{ minWidth: 200 }}>
- <Select
- value={selectedGroup?.id?.toString() || ""}
- onChange={(e) => handleGroupChange(e.target.value)}
- >
- {groups.map((group) => (
- <MenuItem key={group.id} value={group.id.toString()}>
- {group.name}
- </MenuItem>
- ))}
- </Select>
- </FormControl>
- </Grid>
-
- {selectedGroup && (
- <Grid item>
- <LocalizationProvider dateAdapter={AdapterDayjs} adapterLocale="zh-hk">
- <DatePicker
- value={selectedGroup.targetDate && selectedGroup.targetDate !== "" ? dayjs(selectedGroup.targetDate) : null}
- onChange={(date) => {
- if (date) {
- const formattedDate = date.format(INPUT_DATE_FORMAT);
-
- handleGroupTargetDateChange(selectedGroup.id, formattedDate);
- }
- }}
- slotProps={{
- textField: {
- size: "small",
- label: t("Target Date"),
- sx: { width: 180 }
- },
- }}
- />
- </LocalizationProvider>
- </Grid>
- )}
- </>
- )}
- </Grid>
-
- {/* Add group range text */}
- {groups.length > 0 && (
- <Box sx={{ mt: 1 }}>
- <Typography variant="body2" color="text.secondary" sx={{ fontStyle: 'italic' }}>
- {getGroupRangeText()}
- </Typography>
- </Box>
- )}
- </Box>
-
- {/* Second Search Results - Use custom table like AssignAndRelease */}
- {hasSearchedSecond && (
- <Box sx={{ mt: 3 }}>
- <Typography variant="h6" marginBlockEnd={2}>
- {t("Search Results")} ({secondSearchResults.length})
- </Typography>
-
- {selectedSecondSearchItemIds.length > 0 && (
- <Box sx={{ mb: 2 }}>
- <Typography variant="body2" color="text.secondary" sx={{ fontStyle: 'italic' }}>
- {t("Selected items will join above created group")}
- </Typography>
- </Box>
- )}
-
- {isLoadingSecondSearch ? (
- <Typography>{t("Loading...")}</Typography>
- ) : secondSearchResults.length === 0 ? (
- <Typography color="textSecondary">{t("No results found")}</Typography>
- ) : (
- <SearchResultsTable
- items={secondSearchResults}
- selectedItemIds={selectedSecondSearchItemIds}
- groups={groups}
- onItemSelect={handleIndividualCheckboxChange}
- onQtyChange={handleSecondSearchQtyChange}
- onGroupChange={handleCreatedItemGroupChange}
- onQtyBlur={handleQtyBlur}
- isItemInCreated={isItemInCreated}
- pageNum={searchResultsPagingController.pageNum}
- pageSize={searchResultsPagingController.pageSize}
- onPageChange={handleSearchResultsPageChange}
- onPageSizeChange={handleSearchResultsPageSizeChange}
- />
- )}
- </Box>
- )}
-
- {/* Add Submit Button between tables */}
-
- {/* Search Results with SearchResults component */}
- {hasSearchedSecond && secondSearchResults.length > 0 && selectedSecondSearchItemIds.length > 0 && (
- <Box sx={{ mt: 2, mb: 2 }}>
- <Box sx={{ display: 'flex', justifyContent: 'left', alignItems: 'center', gap: 2 }}>
- <Button
- variant="contained"
- color="primary"
- onClick={handleAddSelectedToCreatedItems}
- disabled={selectedSecondSearchItemIds.length === 0 || !areSelectedItemsValid()}
- sx={{ minWidth: 200 }}
- >
- {t("Add Selected Items to Created Items")} ({selectedSecondSearchItemIds.length})
- </Button>
-
- {selectedSecondSearchItemIds.length > 0 && !areSelectedItemsValid() && (
- <Typography
- variant="body2"
- color="error.main"
- sx={{ fontStyle: 'italic' }}
- >
- {getValidationMessage()}
- </Typography>
- )}
- </Box>
- </Box>
- )}
-
-
- {/* 创建项目区域 - 修改Group列为可选择的 */}
- {createdItems.length > 0 && (
- <Box sx={{ mt: 3 }}>
- <Typography variant="h6" marginBlockEnd={2}>
- {t("Created Items")} ({createdItems.length})
- </Typography>
-
- <CreatedItemsTable
- items={createdItems}
- groups={groups}
- onItemSelect={handleCreatedItemSelect}
- onQtyChange={handleQtyChange}
- onGroupChange={handleCreatedItemGroupChange}
- pageNum={createdItemsPagingController.pageNum}
- pageSize={createdItemsPagingController.pageSize}
- onPageChange={handleCreatedItemsPageChange}
- onPageSizeChange={handleCreatedItemsPageSizeChange}
- />
- </Box>
- )}
-
- {/* 操作按钮 */}
- <Stack direction="row" justifyContent="flex-start" gap={1} sx={{ mt: 3 }}>
- <Button
- name="submit"
- variant="contained"
- startIcon={<Check />}
- type="submit"
- disabled={createdItems.filter(item => item.isSelected).length === 0}
- >
- {t("Create Pick Order")}
- </Button>
- <Button
- name="reset"
- variant="outlined"
- onClick={handleReset}
- >
- {t("reset")}
- </Button>
- </Stack>
- </Box>
- </FormProvider>
- );
- };
-
- export default NewCreateItem;
|