|
- "use client";
- import {
- Autocomplete,
- Box,
- Button,
- CircularProgress,
- FormControl,
- Grid,
- Modal,
- TextField,
- Typography,
- Table,
- TableBody,
- TableCell,
- TableContainer,
- TableHead,
- TableRow,
- Paper,
- Checkbox,
- TablePagination,
- } from "@mui/material";
- import { useCallback, useEffect, useMemo, useState } from "react";
- import { useTranslation } from "react-i18next";
- import {
- newassignPickOrder,
- AssignPickOrderInputs,
- releaseAssignedPickOrders, // Add this import
- } from "@/app/api/pickOrder/actions";
- import { fetchNameList, NameList ,fetchNewNameList, NewNameList} from "@/app/api/user/actions";
- import { FormProvider, useForm } from "react-hook-form";
- import { isEmpty, sortBy, uniqBy, upperFirst, groupBy } from "lodash";
- import { OUTPUT_DATE_FORMAT, arrayToDayjs } from "@/app/utils/formatUtil";
- import useUploadContext from "../UploadProvider/useUploadContext";
- import dayjs from "dayjs";
- import arraySupport from "dayjs/plugin/arraySupport";
- import SearchBox, { Criterion } from "../SearchBox";
- import { fetchPickOrderItemsByPageClient } from "@/app/api/settings/item/actions";
-
- dayjs.extend(arraySupport);
-
- interface Props {
- filterArgs: Record<string, any>;
- }
-
- // 使用 fetchPickOrderItemsByPageClient 返回的数据结构
- interface ItemRow {
- id: string;
- pickOrderId: number;
- pickOrderCode: string;
- itemId: number;
- itemCode: string;
- itemName: string;
- requiredQty: number;
- currentStock: number;
- unit: string;
- targetDate: any;
- status: string;
- consoCode?: string;
- assignTo?: number;
- groupName?: string;
- }
-
- // 分组后的数据结构
- interface GroupedItemRow {
- pickOrderId: number;
- pickOrderCode: string;
- targetDate: any;
- status: string;
- consoCode?: string;
- items: ItemRow[];
- }
-
- const style = {
- position: "absolute",
- top: "50%",
- left: "50%",
- transform: "translate(-50%, -50%)",
- bgcolor: "background.paper",
- pt: 5,
- px: 5,
- pb: 10,
- width: { xs: "100%", sm: "100%", md: "100%" },
- };
-
- const AssignAndRelease: React.FC<Props> = ({ filterArgs }) => {
- const { t } = useTranslation("pickOrder");
- const { setIsUploading } = useUploadContext();
-
- // 修复:选择状态改为按 pick order ID 存储
- const [selectedPickOrderIds, setSelectedPickOrderIds] = useState<number[]>([]);
- const [filteredItems, setFilteredItems] = useState<ItemRow[]>([]);
- const [isLoadingItems, setIsLoadingItems] = useState(false);
- const [pagingController, setPagingController] = useState({
- pageNum: 1,
- pageSize: 10,
- });
- const [totalCountItems, setTotalCountItems] = useState<number>();
- const [modalOpen, setModalOpen] = useState(false);
- const [usernameList, setUsernameList] = useState<NewNameList[]>([]);
- const [searchQuery, setSearchQuery] = useState<Record<string, any>>({});
- const [originalItemData, setOriginalItemData] = useState<ItemRow[]>([]);
-
- const formProps = useForm<AssignPickOrderInputs>();
- const errors = formProps.formState.errors;
-
- // 将项目按 pick order 分组
- const groupedItems = useMemo(() => {
- const grouped = groupBy(filteredItems, 'pickOrderId');
- return Object.entries(grouped).map(([pickOrderId, items]) => {
- const firstItem = items[0];
- return {
- pickOrderId: parseInt(pickOrderId),
- pickOrderCode: firstItem.pickOrderCode,
- targetDate: firstItem.targetDate,
- status: firstItem.status,
- consoCode: firstItem.consoCode,
- items: items
- } as GroupedItemRow;
- });
- }, [filteredItems]);
-
- // 修复:处理 pick order 选择
- const handlePickOrderSelect = useCallback((pickOrderId: number, checked: boolean) => {
- if (checked) {
- setSelectedPickOrderIds(prev => [...prev, pickOrderId]);
- } else {
- setSelectedPickOrderIds(prev => prev.filter(id => id !== pickOrderId));
- }
- }, []);
-
- // 修复:检查 pick order 是否被选中
- const isPickOrderSelected = useCallback((pickOrderId: number) => {
- return selectedPickOrderIds.includes(pickOrderId);
- }, [selectedPickOrderIds]);
-
- // 使用 fetchPickOrderItemsByPageClient 获取数据
- const fetchNewPageItems = useCallback(
- async (pagingController: Record<string, number>, filterArgs: Record<string, any>) => {
- console.log("=== fetchNewPageItems called ===");
- console.log("pagingController:", pagingController);
- console.log("filterArgs:", filterArgs);
-
- setIsLoadingItems(true);
- try {
- const params = {
- ...pagingController,
- ...filterArgs,
- // 新增:排除状态为 "assigned" 的提料单
- //status: "pending,released,completed,cancelled" // 或者使用其他方式过滤
- };
- console.log("Final params:", params);
-
- const res = await fetchPickOrderItemsByPageClient(params);
- console.log("API Response:", res);
-
- if (res && res.records) {
- console.log("Records received:", res.records.length);
- console.log("First record:", res.records[0]);
-
- // 新增:在前端也过滤掉 "assigned" 状态的项目
- const filteredRecords = res.records.filter((item: any) => item.status !== "assigned");
-
- const itemRows: ItemRow[] = filteredRecords.map((item: any) => ({
- id: item.id,
- pickOrderId: item.pickOrderId,
- pickOrderCode: item.pickOrderCode,
- itemId: item.itemId,
- itemCode: item.itemCode,
- itemName: item.itemName,
- requiredQty: item.requiredQty,
- currentStock: item.currentStock ?? 0,
- unit: item.unit,
- targetDate: item.targetDate,
- status: item.status,
- consoCode: item.consoCode,
- assignTo: item.assignTo,
- groupName: item.groupName,
- }));
-
- setOriginalItemData(itemRows);
- setFilteredItems(itemRows);
- setTotalCountItems(filteredRecords.length); // 使用过滤后的数量
- } else {
- console.log("No records in response");
- setFilteredItems([]);
- setTotalCountItems(0);
- }
- } catch (error) {
- console.error("Error fetching items:", error);
- setFilteredItems([]);
- setTotalCountItems(0);
- } finally {
- setIsLoadingItems(false);
- }
- },
- [],
- );
-
- const searchCriteria: Criterion<any>[] = useMemo(
- () => [
- {
- label: t("Pick Order Code"),
- paramName: "pickOrderCode",
- type: "text",
- },
- {
- label: t("Item Code"),
- paramName: "itemCode",
- type: "text"
- },
- {
- label: t("Group Code"),
- paramName: "groupName",
- type: "text",
- },
- {
- label: t("Item Name"),
- paramName: "itemName",
- type: "text",
- },
- {
- label: t("Target Date From"),
- label2: t("Target Date To"),
- paramName: "targetDate",
- type: "dateRange",
- },
-
- {
- label: t("Pick Order Status"),
- paramName: "status",
- type: "autocomplete",
- options: sortBy(
- uniqBy(
- originalItemData.map((item) => ({
- value: item.status,
- label: t(upperFirst(item.status)),
- })),
- "value",
- ),
- "label",
- ),
- },
- ],
- [originalItemData, t],
- );
-
- const handleSearch = useCallback((query: Record<string, any>) => {
- setSearchQuery({ ...query });
- console.log("Search query:", query);
-
- const filtered = originalItemData.filter((item) => {
- const itemTargetDateStr = arrayToDayjs(item.targetDate);
-
- const itemCodeMatch = !query.itemCode ||
- item.itemCode?.toLowerCase().includes((query.itemCode || "").toLowerCase());
-
- const itemNameMatch = !query.itemName ||
- item.itemName?.toLowerCase().includes((query.itemName || "").toLowerCase());
-
- const pickOrderCodeMatch = !query.pickOrderCode ||
- item.pickOrderCode?.toLowerCase().includes((query.pickOrderCode || "").toLowerCase());
- const groupNameMatch = !query.groupName ||
- item.groupName?.toLowerCase().includes((query.groupName || "").toLowerCase());
- // 日期范围搜索
- let dateMatch = true;
- if (query.targetDate || query.targetDateTo) {
- try {
- if (query.targetDate && !query.targetDateTo) {
- const fromDate = dayjs(query.targetDate);
- dateMatch = itemTargetDateStr.isSame(fromDate, 'day') ||
- itemTargetDateStr.isAfter(fromDate, 'day');
- } else if (!query.targetDate && query.targetDateTo) {
- const toDate = dayjs(query.targetDateTo);
- dateMatch = itemTargetDateStr.isSame(toDate, 'day') ||
- itemTargetDateStr.isBefore(toDate, 'day');
- } else if (query.targetDate && query.targetDateTo) {
- const fromDate = dayjs(query.targetDate);
- const toDate = dayjs(query.targetDateTo);
- dateMatch = (itemTargetDateStr.isSame(fromDate, 'day') ||
- itemTargetDateStr.isAfter(fromDate, 'day')) &&
- (itemTargetDateStr.isSame(toDate, 'day') ||
- itemTargetDateStr.isBefore(toDate, 'day'));
- }
- } catch (error) {
- console.error("Date parsing error:", error);
- dateMatch = true;
- }
- }
-
- const statusMatch = !query.status ||
- query.status.toLowerCase() === "all" ||
- item.status?.toLowerCase().includes((query.status || "").toLowerCase());
-
- return itemCodeMatch && itemNameMatch && groupNameMatch && pickOrderCodeMatch && dateMatch && statusMatch;
- });
-
- console.log("Filtered items count:", filtered.length);
- setFilteredItems(filtered);
- }, [originalItemData]);
-
- const handleReset = useCallback(() => {
- setSearchQuery({});
- setFilteredItems(originalItemData);
- setTimeout(() => {
- setSearchQuery({});
- }, 0);
- }, [originalItemData]);
-
- // 修复:处理分页变化
- const handlePageChange = useCallback((event: unknown, newPage: number) => {
- const newPagingController = {
- ...pagingController,
- pageNum: newPage + 1, // API 使用 1-based 分页
- };
- setPagingController(newPagingController);
- }, [pagingController]);
-
- const handlePageSizeChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
- const newPageSize = parseInt(event.target.value, 10);
- const newPagingController = {
- pageNum: 1, // 重置到第一页
- pageSize: newPageSize,
- };
- setPagingController(newPagingController);
- }, []);
-
- const handleAssignOnly = useCallback(async (data: AssignPickOrderInputs) => {
- if (selectedPickOrderIds.length === 0) return;
-
- setIsUploading(true);
- try {
- // 修复:直接使用选中的 pick order IDs
- const assignRes = await newassignPickOrder({
- pickOrderIds: selectedPickOrderIds,
- assignTo: data.assignTo,
- });
-
- if (assignRes && assignRes.code === "SUCCESS") {
- console.log("Assign successful:", assignRes);
- setModalOpen(false);
- setSelectedPickOrderIds([]); // 清空选择
- fetchNewPageItems(pagingController, filterArgs);
- } else {
- console.error("Assign failed:", assignRes);
- }
- } catch (error) {
- console.error("Error in assign:", error);
- } finally {
- setIsUploading(false);
- }
- }, [selectedPickOrderIds, setIsUploading, fetchNewPageItems, pagingController, filterArgs]);
-
- const handleAssignAndReleaseCombined = useCallback(async (data: AssignPickOrderInputs) => {
- if (selectedPickOrderIds.length === 0) return;
-
- setIsUploading(true);
- try {
- // Step 1: Assign the pick orders
- const assignRes = await newassignPickOrder({
- pickOrderIds: selectedPickOrderIds,
- assignTo: data.assignTo,
- });
-
- if (assignRes && assignRes.code === "SUCCESS") {
- console.log("Assign successful:", assignRes);
-
- // Step 2: Release the assigned pick orders
- const releaseRes = await releaseAssignedPickOrders({
- pickOrderIds: selectedPickOrderIds,
- assignTo: data.assignTo,
- });
-
- if (releaseRes && releaseRes.code === "SUCCESS") {
- console.log("Assign and Release successful:", releaseRes);
- setModalOpen(false);
- setSelectedPickOrderIds([]); // 清空选择
- fetchNewPageItems(pagingController, filterArgs);
- } else {
- console.error("Release failed:", releaseRes);
- }
- } else {
- console.error("Assign failed:", assignRes);
- }
- } catch (error) {
- console.error("Error in assign and release:", error);
- } finally {
- setIsUploading(false);
- }
- }, [selectedPickOrderIds, setIsUploading, fetchNewPageItems, pagingController, filterArgs]);
-
- const openAssignModal = useCallback(() => {
- setModalOpen(true);
- formProps.reset();
- }, [formProps]);
-
- // 组件挂载时加载数据
- useEffect(() => {
- console.log("=== Component mounted ===");
- fetchNewPageItems(pagingController, filterArgs || {});
- }, []); // 只在组件挂载时执行一次
-
- // 当 pagingController 或 filterArgs 变化时重新调用 API
- useEffect(() => {
- console.log("=== Dependencies changed ===");
- if (pagingController && (filterArgs || {})) {
- fetchNewPageItems(pagingController, filterArgs || {});
- }
- }, [pagingController, filterArgs, fetchNewPageItems]);
-
- useEffect(() => {
- const loadUsernameList = async () => {
- try {
- const res = await fetchNewNameList();
- if (res) {
- setUsernameList(res);
- }
- } catch (error) {
- console.error("Error loading username list:", error);
- }
- };
- loadUsernameList();
- }, []);
-
- // 自定义分组表格组件
- const CustomGroupedTable = () => {
- return (
- <>
- <TableContainer component={Paper}>
- <Table>
- <TableHead>
- <TableRow>
- <TableCell>{t("Selected")}</TableCell>
- <TableCell>{t("Pick Order Code")}</TableCell>
- <TableCell>{t("Group Code")}</TableCell>
- <TableCell>{t("Item Code")}</TableCell>
- <TableCell>{t("Item Name")}</TableCell>
- <TableCell align="right">{t("Order Quantity")}</TableCell>
- <TableCell align="right">{t("Current Stock")}</TableCell>
- <TableCell align="right">{t("Stock Unit")}</TableCell>
- <TableCell>{t("Target Date")}</TableCell>
- <TableCell>{t("Pick Order Status")}</TableCell>
- </TableRow>
- </TableHead>
- <TableBody>
- {groupedItems.length === 0 ? (
- <TableRow>
- <TableCell colSpan={9} align="center">
- <Typography variant="body2" color="text.secondary">
- {t("No data available")}
- </Typography>
- </TableCell>
- </TableRow>
- ) : (
- groupedItems.map((group) => (
- group.items.map((item, index) => (
- <TableRow key={item.id}>
- {/* Checkbox - 只在第一个项目显示,按 pick order 选择 */}
- <TableCell>
- {index === 0 ? (
- <Checkbox
- checked={isPickOrderSelected(group.pickOrderId)}
- onChange={(e) => handlePickOrderSelect(group.pickOrderId, e.target.checked)}
- disabled={!isEmpty(item.consoCode)}
- />
- ) : null}
- </TableCell>
- {/* Pick Order Code - 只在第一个项目显示 */}
- <TableCell>
- {index === 0 ? item.pickOrderCode : null}
- </TableCell>
- {/* Group Name */}
- <TableCell>
- {index === 0 ? (item.groupName || "No Group") : null}
- </TableCell>
- {/* Item Code */}
- <TableCell>{item.itemCode}</TableCell>
- {/* Item Name */}
- <TableCell>{item.itemName}</TableCell>
-
- {/* Order Quantity */}
- <TableCell align="right">{item.requiredQty}</TableCell>
-
- {/* Current Stock */}
- <TableCell align="right">
- <Typography
- variant="body2"
- color={item.currentStock > 0 ? "success.main" : "error.main"}
- sx={{ fontWeight: item.currentStock > 0 ? 'bold' : 'normal' }}
- >
- {item.currentStock.toLocaleString()}
- </Typography>
- </TableCell>
-
- {/* Unit */}
- <TableCell align="right">{item.unit}</TableCell>
-
- {/* Target Date - 只在第一个项目显示 */}
- <TableCell>
- {index === 0 ? (
- arrayToDayjs(item.targetDate)
- .add(-1, "month")
- .format(OUTPUT_DATE_FORMAT)
- ) : null}
- </TableCell>
-
- {/* Pick Order Status - 只在第一个项目显示 */}
- <TableCell>
- {index === 0 ? upperFirst(item.status) : null}
- </TableCell>
- </TableRow>
- ))
- ))
- )}
- </TableBody>
- </Table>
- </TableContainer>
-
- {/* 修复:添加分页组件 */}
- <TablePagination
- component="div"
- count={totalCountItems || 0}
- page={(pagingController.pageNum - 1)} // 转换为 0-based
- rowsPerPage={pagingController.pageSize}
- onPageChange={handlePageChange}
- onRowsPerPageChange={handlePageSizeChange}
- rowsPerPageOptions={[10, 25, 50]}
- labelRowsPerPage={t("Rows per page")}
- labelDisplayedRows={({ from, to, count }) =>
- `${from}-${to} of ${count !== -1 ? count : `more than ${to}`}`
- }
- />
- </>
- );
- };
-
- return (
- <>
- <SearchBox criteria={searchCriteria} onSearch={handleSearch} onReset={handleReset} />
- <Grid container rowGap={1}>
- <Grid item xs={12}>
- {isLoadingItems ? (
- <CircularProgress size={40} />
- ) : (
- <CustomGroupedTable />
- )}
- </Grid>
- <Grid item xs={12}>
- <Box sx={{ display: "flex", justifyContent: "flex-start", mt: 2 }}>
- <Button
- disabled={selectedPickOrderIds.length < 1}
- variant="outlined"
- onClick={openAssignModal}
- >
- {t("Assign")}
- </Button>
- </Box>
- </Grid>
- </Grid>
-
- {modalOpen ? (
- <Modal
- open={modalOpen}
- onClose={() => setModalOpen(false)}
- aria-labelledby="modal-modal-title"
- aria-describedby="modal-modal-description"
- >
- <Box sx={style}>
- <Grid container rowGap={2}>
- <Grid item xs={12}>
- <Typography variant="h6" component="h2">
- {t("Assign Pick Orders")}
- </Typography>
- </Grid>
- <Grid item xs={12}>
- <Typography variant="body1" color="text.secondary">
- {t("Selected Pick Orders")}: {selectedPickOrderIds.length}
- </Typography>
- </Grid>
- <Grid item xs={12}>
- <FormProvider {...formProps}>
- <form>
- <Grid container spacing={2}>
- <Grid item xs={12}>
- <FormControl fullWidth>
- <Autocomplete
- options={usernameList}
- getOptionLabel={(option) => {
- // 修改:显示更详细的用户信息
- const title = option.title ? ` (${option.title})` : '';
- const department = option.department ? ` - ${option.department}` : '';
- return `${option.name}${title}${department}`;
- }}
- renderOption={(props, option) => (
- <Box component="li" {...props}>
- <Typography variant="body1">
- {option.name}
- {option.title && ` (${option.title})`}
- {option.department && ` - ${option.department}`}
- </Typography>
- </Box>
- )}
- onChange={(_, value) => {
- formProps.setValue("assignTo", value?.id || 0);
- }}
- renderInput={(params) => (
- <TextField
- {...params}
- label={t("Assign To")}
- error={!!errors.assignTo}
- helperText={errors.assignTo?.message}
- required
- />
- )}
- />
- </FormControl>
- </Grid>
- <Grid item xs={12}>
- <Typography variant="body2" color="warning.main">
- {t("Select an action for the assigned pick orders.")}
- </Typography>
- </Grid>
- <Grid item xs={12}>
- <Box sx={{ display: "flex", gap: 2, justifyContent: "flex-end" }}>
- <Button variant="outlined" onClick={() => setModalOpen(false)}>
- {t("Cancel")}
- </Button>
- <Button
- variant="contained"
- color="primary"
- onClick={formProps.handleSubmit(handleAssignOnly)}
- >
- {t("Assign")}
- </Button>
- <Button
- variant="contained"
- color="secondary"
- onClick={formProps.handleSubmit(handleAssignAndReleaseCombined)}
- >
- {t("Assign and Release")}
- </Button>
- </Box>
- </Grid>
- </Grid>
- </form>
- </FormProvider>
- </Grid>
- </Grid>
- </Box>
- </Modal>
- ) : undefined}
- </>
- );
- };
-
- export default AssignAndRelease;
|