|
- "use client";
-
- import { createPickOrder, SavePickOrderRequest, SavePickOrderLineRequest } from "@/app/api/pickOrder/actions";
- import {
- Autocomplete,
- Box,
- Button,
- FormControl,
- Grid,
- Stack,
- TextField,
- Typography,
- Checkbox,
- Table,
- TableBody,
- TableCell,
- TableContainer,
- TableHead,
- TableRow,
- Paper,
- } 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 } 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";
- type Props = {
- filterArgs?: Record<string, any>;
- searchQuery?: Record<string, any>;
- };
-
- // 扩展表单类型以包含搜索字段
- interface SearchFormData extends SavePickOrderRequest {
- searchCode?: string;
- searchName?: string;
- }
-
- interface CreatedItem {
- itemId: number;
- itemName: string;
- itemCode: string;
- qty: number;
- uom: string;
- uomId: number;
- isSelected: boolean;
- currentStockBalance?: number;
- targetDate?: string; // Make it optional to match the source
- }
-
- // 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;
- }
- interface JobOrderDetailPickLine {
- id: number;
- code: string;
- name: string;
- lotNo: string | null;
- reqQty: number;
- uom: string;
- status: string;
- }
- const NewCreateItem: React.FC<Props> = ({ filterArgs, searchQuery }) => {
- 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);
-
- // 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 || []);
-
- // Convert Job Order items to SearchItemWithQty format
- const convertedItems = (jobOrderDetail.pickLines || []).map(item => ({
- id: item.id,
- label: item.name,
- qty: item.reqQty, // Pre-fill with required quantity
- uom: item.uom,
- uomId: 0, // We'll need to get this from the item lookup
- 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 && items.length > 0) {
- const hasValidSearch = (
- (searchQuery.items && searchQuery.items.trim && searchQuery.items.trim() !== "") ||
- (searchQuery.code && searchQuery.code.trim && searchQuery.code.trim() !== "") ||
- (searchQuery.type && searchQuery.type !== "All")
- );
-
- if (hasValidSearch) {
- let filtered = items;
-
- if (searchQuery.items) {
- const itemsToSearch = Array.isArray(searchQuery.items)
- ? searchQuery.items
- : [searchQuery.items];
-
- if (itemsToSearch.length > 0 && !itemsToSearch.includes("All")) {
- filtered = filtered.filter(item =>
- itemsToSearch.some((searchItem: string) =>
- item.label.toLowerCase().includes(searchItem.toLowerCase())
- )
- );
- }
- }
-
- if (searchQuery.code) {
- filtered = filtered.filter(item =>
- item.label.toLowerCase().includes(searchQuery.code.toLowerCase())
- );
- }
-
- if (searchQuery.type && searchQuery.type !== "All") {
- // filtered = filtered.filter(item => item.type === searchQuery.type);
- }
-
- // Convert to SearchItemWithQty with default qty = null
- const filteredWithQty = filtered.slice(0, 10).map(item => ({
- ...item,
- qty: null // Changed from 1 to null
- }));
- setFilteredItems(filteredWithQty);
- setHasSearched(true);
- } else {
- setFilteredItems([]);
- setHasSearched(false);
- }
- } else {
- setFilteredItems([]);
- setHasSearched(false);
- }
- }, [searchQuery, items]);
-
- 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 (!type) {
- alert(t("Please select type"));
- return;
- }
-
- 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;
- }
-
- const newCreatedItem: CreatedItem = {
- itemId: item.id,
- itemName: item.label,
- itemCode: item.label,
- qty: item.qty || 1,
- uom: item.uom || "",
- uomId: item.uomId || 0,
- isSelected: true,
- currentStockBalance: item.currentStockBalance,
- targetDate: item.targetDate || targetDate, // Use item's targetDate or fallback to form's targetDate
- };
- 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]);
-
- 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;
- }
-
- if (!data.type) {
- alert(t("Please select product type"));
- return;
- }
-
- if (!data.targetDate) {
- alert(t("Please select target date"));
- return;
- }
-
-
- let formattedTargetDate = data.targetDate;
- if (data.targetDate && typeof data.targetDate === 'string') {
- try {
- const date = dayjs(data.targetDate);
- formattedTargetDate = date.format('YYYY-MM-DD');
- } catch (error) {
- console.error("Invalid date format:", data.targetDate);
- alert(t("Invalid date format"));
- return;
- }
- }
-
-
- const pickOrderData: SavePickOrderRequest = {
- type: data.type || "Consumable",
- targetDate: formattedTargetDate,
- pickOrderLine: selectedCreatedItems.map(item => ({
- itemId: item.itemId,
- qty: item.qty,
- uomId: item.uomId
- } as SavePickOrderLineRequest))
- };
-
- console.log("Submitting pick order:", pickOrderData);
-
- try {
- const res = await createPickOrder(pickOrderData);
- if (res.id) {
- console.log("Pick order created successfully:", res);
-
- setCreatedItems(prev => prev.filter(item => !item.isSelected));
- formProps.reset();
- setHasSearched(false);
- setFilteredItems([]);
- alert(t("Pick order created successfully"));
- }
- } catch (error) {
- console.error("Error creating pick order:", error);
- alert(t("Failed to create pick order"));
- }
- },
- [createdItems, t, formProps]
- );
-
- const handleReset = useCallback(() => {
- formProps.reset();
- setCreatedItems([]);
- setHasSearched(false);
- setFilteredItems([]);
- }, [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 checkbox change handler for first search
- 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) {
- filteredItems.forEach(item => {
- if (!isItemInCreated(item.id)) {
- handleSearchItemSelect(item.id, true);
- }
- });
- } else {
-
- 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]);
-
- // 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);
- }}
- onKeyDown={(e) => {
- // Allow typing numbers, backspace, delete, arrow keys
- if (!/[0-9]/.test(e.key) &&
- !['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab', 'Enter'].includes(e.key)) {
- e.preventDefault();
- }
- }}
- 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("Unit"),
- renderCell: (item) => item.uom || "-",
- },
- ], [t, isItemInCreated, handleSearchQtyChange]);
- const pickOrderSearchCriteria: Criterion<any>[] = useMemo(
- () => [
- {
- label: t("Product Type"),
- paramName: "type",
- type: "autocomplete",
- options: [
- { value: "Consumable", label: t("Consumable") },
- { value: "MATERIAL", label: t("Material") },
- { value: "JOB_ORDER", label: t("Job Order") }
- ],
- },
- {
- label: t("Item Code"),
- paramName: "code",
- type: "text"
- },
- {
- label: t("Item Name"),
- paramName: "name",
- type: "text"
- },
- ],
- [t],
- );
-
- // Add search handler for second search (same as first search)
- const handleSecondSearch = useCallback((query: Record<string, any>) => {
- console.log("Second search triggered with query:", query);
- setSecondSearchQuery({ ...query });
- setIsLoadingSecondSearch(true);
-
- // 同步第二个搜索框的信息到表单 - 确保类型值正确
- if (query.type) {
- // 确保类型值符合后端枚举格式
- 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);
- }
-
- // 设置默认目标日期为今天
- const today = dayjs().format(INPUT_DATE_FORMAT);
- formProps.setValue("targetDate", today);
-
- 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 default qty = null and today's date
- const filteredWithQty = filtered.slice(0, 100).map(item => ({
- ...item,
- qty: null,
- targetDate: today, // 使用今天的日期作为默认值
- }));
-
- setSecondSearchResults(filteredWithQty);
- setHasSearchedSecond(true);
- setIsLoadingSecondSearch(false);
- }, 500);
- }, [items, formProps]);
-
- // Add reset handler for second search
- 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]);
-
- // Add quantity change handler for second search
- const handleSecondSearchQtyChange = useCallback((itemId: number, newQty: number | null) => {
- setSecondSearchResults(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
- )
- );
- }, []);
-
- // Add item selection handler for second search
- const handleSecondSearchItemSelect = useCallback((itemId: number, isSelected: boolean) => {
- if (isSelected) {
- const item = secondSearchResults.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;
- }
-
- const newCreatedItem: CreatedItem = {
- itemId: item.id,
- itemName: item.label,
- itemCode: item.label,
- qty: item.qty || 1,
- uom: item.uom || "",
- uomId: item.uomId || 0,
- isSelected: true,
- currentStockBalance: item.currentStockBalance,
- targetDate: item.targetDate || targetDate,
- };
- setCreatedItems(prev => [...prev, newCreatedItem]);
- }
- }, [secondSearchResults, createdItems, t, targetDate]);
-
- // 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)) {
- handleSecondSearchItemSelect(item.id, true);
- }
- });
- } else {
- // 部分选择:只处理当前页面的选择
- secondSearchResults.forEach(item => {
- const isSelected = newIds.includes(item.id);
- const isCurrentlyInCreated = isItemInCreated(item.id);
-
- if (isSelected && !isCurrentlyInCreated) {
- handleSecondSearchItemSelect(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)) {
- handleSecondSearchItemSelect(id as number, true);
- }
- });
-
- newlyDeselected.forEach(id => {
- setCreatedItems(prev => prev.filter(createdItem => createdItem.itemId !== id));
- });
- }
- }, [selectedSecondSearchItemIds, secondSearchResults, isItemInCreated, handleSecondSearchItemSelect]);
-
- // Define columns for second search (same as first search but with different handlers)
- const secondSearchItemColumns: Column<SearchItemWithQty>[] = useMemo(() => [
- {
- name: "id",
- label: "",
- type: "checkbox",
- disabled: (item) => isItemInCreated(item.id),
- },
- {
- name: "label",
- label: t("Item"),
- renderCell: (item) => {
- // 格式化标签显示:将 "CODE - NAME" 格式化为更友好的显示
- 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"),
- 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: "uom",
- label: t("Unit"),
- renderCell: (item) => item.uom || "-",
- },
- {
- name: "qty",
- label: t("Order Quantity"),
- renderCell: (item) => (
- <TextField
- type="number"
- size="small"
- value={item.qty || ""}
- onChange={(e) => {
- const value = e.target.value;
- const numValue = value === "" ? null : Number(value);
- handleSecondSearchQtyChange(item.id, numValue);
- }}
- onKeyDown={(e) => {
- if (!/[0-9]/.test(e.key) &&
- !['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab', 'Enter'].includes(e.key)) {
- e.preventDefault();
- }
- }}
- inputProps={{
- min: 1,
- step: 1,
- style: { textAlign: 'center' }
- }}
- sx={{
- width: '80px',
- '& .MuiInputBase-input': {
- textAlign: 'center',
- cursor: 'text'
- }
- }}
- />
- ),
- },
- {
- name: "targetDate",
- label: t("Target Date"),
- renderCell: (item) => (
- <LocalizationProvider dateAdapter={AdapterDayjs} adapterLocale="zh-hk">
- <DatePicker
- value={item.targetDate ? dayjs(item.targetDate) : dayjs()}
- onChange={(date) => {
- if (date) {
- const formattedDate = date.format(INPUT_DATE_FORMAT);
- // 更新搜索结果中的目标日期
- setSecondSearchResults(prev =>
- prev.map(searchItem =>
- searchItem.id === item.id ? { ...searchItem, targetDate: formattedDate } : searchItem
- )
- );
- // 更新创建项目中的目标日期
- setCreatedItems(prev =>
- prev.map(createdItem =>
- createdItem.itemId === item.id ? { ...createdItem, targetDate: formattedDate } : createdItem
- )
- );
- // 更新表单中的目标日期
- formProps.setValue("targetDate", formattedDate);
- }
- }}
- slotProps={{
- textField: {
- size: "small",
- sx: {
- width: '160px', // 增加宽度以显示完整日期
- '& .MuiInputBase-input': {
- fontSize: '0.875rem' // 稍微减小字体以适应更多内容
- }
- }
- },
- }}
- />
- </LocalizationProvider>
- ),
- },
-
- ], [t, isItemInCreated, handleSecondSearchQtyChange, formProps]);
-
- return (
- <FormProvider {...formProps}>
- <Box
- component="form"
- onSubmit={formProps.handleSubmit(onSubmit)}
- >
- {/*<Grid container spacing={2}>
- <Grid item xs={12}>
- <Typography variant="h6" display="block" marginBlockEnd={1}>
- {t("Pick Order Detail")}
- </Typography>
- </Grid>
- {/*
- <Grid item xs={2}>
- <FormControl fullWidth>
- <Autocomplete
- disableClearable
- fullWidth
- getOptionLabel={(option) => option.type}
- options={typeList}
- onChange={handleTypeChange}
- renderInput={(params) => <TextField {...params} label={t("Product Type")} required/>}
- />
- </FormControl>
- </Grid>
-
- <Grid item xs={2}>
- <Controller
- control={formProps.control}
- name="searchCode"
- render={({ field }) => (
- <TextField
- {...field}
- fullWidth
- label={t("Pick Order Code")}
- //placeholder={t("Enter Pick Order Code")}
- />
- )}
- />
- </Grid>
-
- <Grid item xs={2}>
- <Controller
- control={formProps.control}
- name="searchName"
- render={({ field }) => (
- <TextField
- {...field}
- fullWidth
- label={t("name")}
- //placeholder={t("Enter item name")}
- />
- )}
- />
- </Grid>
-
- <Grid item xs={3}>
- <Controller
- control={formProps.control}
- name="targetDate"
- render={({ field }) => (
- <LocalizationProvider
- dateAdapter={AdapterDayjs}
- adapterLocale="zh-hk"
- >
- <DatePicker
- {...field}
- sx={{ width: "100%" }}
- label={t("targetDate")}
- value={targetDate ? dayjs(targetDate) : undefined}
- onChange={(date) => {
- if (!date) return;
- formProps.setValue("targetDate", date.format(INPUT_DATE_FORMAT));
- }}
- inputRef={field.ref}
- slotProps={{
- textField: {
- error: Boolean(errors.targetDate?.message),
- helperText: errors.targetDate?.message,
- },
- }}
- />
- </LocalizationProvider>
- )}
- />
- </Grid>
-
- <Grid item xs={3}>
- <Button
- variant="contained"
- startIcon={<Search />}
- onClick={handleSearch}
- disabled={!type || (!searchCode && !searchName) || isLoading}
- fullWidth
- sx={{ height: '56px' }}
- >
- {isLoading ? t("Searching...") : t("Search")}
- </Button>
- </Grid>
- </Grid>
- */}
-
- {/* First Search Box - Item Search */}
- <Box sx={{ mt: 3, mb: 2 }}>
- <Typography variant="h6" display="block" marginBlockEnd={1}>
- {t("Search Items")}
- </Typography>
-
- <SearchBox
- criteria={pickOrderSearchCriteria}
- onSearch={handleSecondSearch}
- onReset={handleSecondReset}
- />
- </Box>
-
- {/* Second Search Results */}
- {hasSearchedSecond && (
- <Box sx={{ mt: 3 }}>
- <Typography variant="h6" marginBlockEnd={2}>
- {t("Search Results")} ({secondSearchResults.length})
- </Typography>
-
- {isLoadingSecondSearch ? (
- <Typography>{t("Loading...")}</Typography>
- ) : secondSearchResults.length === 0 ? (
- <Typography color="textSecondary">{t("No results found")}</Typography>
- ) : (
- <SearchResults<SearchItemWithQty>
- items={secondSearchResults}
- columns={secondSearchItemColumns}
- totalCount={secondSearchResults.length}
- checkboxIds={selectedSecondSearchItemIds}
- setCheckboxIds={handleSecondSearchCheckboxChange}
- />
- )}
- </Box>
- )}
-
- {/* Search Results with SearchResults component */}
- {hasSearched && filteredItems.length > 0 && (
- <Box sx={{ mt: 3 }}>
- <Typography variant="h6" marginBlockEnd={2}>
- {t("Search Results")} ({filteredItems.length})
- {filteredItems.length >= 100 && (
- <Typography variant="caption" color="textSecondary" sx={{ ml: 2 }}>
- {t("Showing first 100 results")}
- </Typography>
- )}
- </Typography>
-
- <SearchResults<SearchItemWithQty>
- items={filteredItems}
- columns={searchItemColumns}
- totalCount={filteredItems.length}
- checkboxIds={selectedSearchItemIds}
- setCheckboxIds={handleSearchCheckboxChange}
- />
- </Box>
- )}
-
-
- {/* 创建项目区域 */}
- {createdItems.length > 0 && (
- <Box sx={{ mt: 3 }}>
- <Typography variant="h6" marginBlockEnd={2}>
- {t("Created Items")} ({createdItems.length})
- </Typography>
-
- <TableContainer component={Paper}>
- <Table>
- <TableHead>
- <TableRow>
- <TableCell
- padding="checkbox"
- sx={{
- minWidth: '120px', // 增加最小宽度以适应 "Selected" 文本
- width: '120px' // 固定宽度
- }}
- >
- <Typography variant="subtitle2">{t("Selected")}</Typography>
- </TableCell>
- <TableCell>
- <Typography variant="subtitle2">{t("Item")}</Typography>
- </TableCell>
-
- <TableCell>
- <Typography variant="subtitle2">{t("Current Stock")}</Typography>
- </TableCell>
- <TableCell>
- <Typography variant="subtitle2">{t("Unit")}</Typography>
- </TableCell>
- <TableCell>
- <Typography variant="subtitle2">{t("Order Quantity")}</Typography>
- </TableCell>
- <TableCell>
- <Typography variant="subtitle2">{t("Target Date")}</Typography>
- </TableCell>
-
- </TableRow>
- </TableHead>
- <TableBody>
- {createdItems.map((item) => (
- <TableRow key={item.itemId}>
- <TableCell
- padding="checkbox"
- sx={{
- minWidth: '120px', // 保持与表头一致的宽度
- width: '120px'
- }}
- >
- <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>
- <Typography
- variant="body2"
- color={item.currentStockBalance && item.currentStockBalance > 0 ? "success.main" : "error.main"}
- >
- {item.currentStockBalance || 0}
- </Typography>
- </TableCell>
- <TableCell>
- <Typography variant="body2">{item.uom}</Typography>
- </TableCell>
- <TableCell>
- <TextField
- type="number"
- size="small"
- value={item.qty}
- onChange={(e) => {
- const newQty = Number(e.target.value);
- handleQtyChange(item.itemId, newQty);
-
- setFilteredItems(prev =>
- prev.map(searchItem =>
- searchItem.id === item.itemId ? { ...searchItem, qty: newQty } : searchItem
- )
- );
- }}
- onKeyDown={(e) => {
- if (!/[0-9]/.test(e.key) &&
- !['Backspace', 'Delete', 'ArrowLeft', 'ArrowRight', 'Tab', 'Enter'].includes(e.key)) {
- e.preventDefault();
- }
- }}
- inputProps={{
- min: 1,
- step: 1,
- style: { textAlign: 'center' }
- }}
- sx={{
- width: '80px',
- '& .MuiInputBase-input': {
- textAlign: 'center',
- cursor: 'text'
- }
- }}
- />
- </TableCell>
- <TableCell>
- <LocalizationProvider dateAdapter={AdapterDayjs} adapterLocale="zh-hk">
- <DatePicker
- value={item.targetDate ? dayjs(item.targetDate) : dayjs()}
- onChange={(date) => {
- if (date) {
- const formattedDate = date.format(INPUT_DATE_FORMAT);
- handleQtyChange(item.itemId, item.qty); // 触发重新渲染
- setCreatedItems(prev =>
- prev.map(createdItem =>
- createdItem.itemId === item.itemId ? { ...createdItem, targetDate: formattedDate } : createdItem
- )
- );
- formProps.setValue("targetDate", formattedDate);
- }
- }}
- slotProps={{
- textField: {
- size: "small",
- sx: {
- width: '180px', // 增加宽度以显示完整日期
- '& .MuiInputBase-input': {
- fontSize: '0.875rem' // 稍微减小字体以适应更多内容
- }
- }
- },
- }}
- />
- </LocalizationProvider>
- </TableCell>
- </TableRow>
- ))}
- </TableBody>
- </Table>
- </TableContainer>
- </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;
|