| @@ -15,15 +15,14 @@ import { | |||
| Paper, | |||
| CircularProgress, | |||
| TablePagination, | |||
| FormControl, | |||
| Select, | |||
| MenuItem, | |||
| Stack | |||
| } from '@mui/material'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import dayjs from 'dayjs'; | |||
| import { arrayToDayjs } from '@/app/utils/formatUtil'; | |||
| import { fetchMaterialPickStatus, MaterialPickStatusItem } from '@/app/api/jo/actions'; | |||
| import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers"; | |||
| import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; | |||
| const REFRESH_INTERVAL = 10 * 60 * 1000; // 10 minutes in milliseconds | |||
| @@ -242,16 +241,19 @@ const MaterialPickStatusTable: React.FC = () => { | |||
| {/* Filters */} | |||
| <Stack direction="row" spacing={2} sx={{ mb: 3 }}> | |||
| <FormControl size="small" sx={{ minWidth: 160 }}> | |||
| <Select | |||
| value={selectedDate} | |||
| onChange={(e) => setSelectedDate(e.target.value)} | |||
| > | |||
| <MenuItem value={dayjs().format("YYYY-MM-DD")}>{t("Today")}</MenuItem> | |||
| <MenuItem value={dayjs().subtract(1, "day").format("YYYY-MM-DD")}>{t("Yesterday")}</MenuItem> | |||
| <MenuItem value={dayjs().subtract(2, "day").format("YYYY-MM-DD")}>{t("Two Days Ago")}</MenuItem> | |||
| </Select> | |||
| </FormControl> | |||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||
| <DatePicker | |||
| label={t("Date")} | |||
| value={dayjs(selectedDate)} | |||
| onChange={(newValue) => { | |||
| setSelectedDate(newValue ? newValue.format("YYYY-MM-DD") : selectedDate); | |||
| }} | |||
| format="YYYY-MM-DD" | |||
| slotProps={{ | |||
| textField: { size: "small", sx: { minWidth: 160 } }, | |||
| }} | |||
| /> | |||
| </LocalizationProvider> | |||
| <Box sx={{ flexGrow: 1 }} /> | |||
| <Stack direction="row" spacing={2} sx={{ alignSelf: 'center' }}> | |||
| @@ -46,6 +46,7 @@ import { useSession } from "next-auth/react"; | |||
| import { SessionWithTokens } from "@/config/authConfig"; | |||
| import Swal from "sweetalert2"; | |||
| import { PrinterCombo } from "@/app/api/settings/printer"; | |||
| import dayjs from "dayjs"; | |||
| interface Props { | |||
| filterArgs: Record<string, any>; | |||
| printerCombo: PrinterCombo[]; | |||
| @@ -61,14 +62,15 @@ interface CompletedJobOrderPickOrder { | |||
| pickOrderConsoCode: string; | |||
| pickOrderTargetDate: string; | |||
| pickOrderStatus: string; | |||
| completedDate: string; | |||
| // Backend可能返回 [yyyy, MM, dd, HH, mm, ss] 或字符串,前端统一在 helper 里转成 YYYY-MM-DD | |||
| completedDate: any; | |||
| jobOrderId: number; | |||
| jobOrderCode: string; | |||
| jobOrderName: string; | |||
| reqQty: number; | |||
| uom: string; | |||
| planStart: string; | |||
| planEnd: string; | |||
| planStart: any; | |||
| planEnd: any; | |||
| secondScanCompleted: boolean; | |||
| totalItems: number; | |||
| completedItems: number; | |||
| @@ -193,33 +195,10 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ | |||
| } | |||
| }, [currentUserId, fetchCompletedJobOrderPickOrdersData]); | |||
| // 修改:搜索功能 | |||
| // 修改:搜索功能(只更新 query;实际过滤交给 useEffect + date filter 统一处理) | |||
| const handleSearch = useCallback((query: Record<string, any>) => { | |||
| setSearchQuery({ ...query }); | |||
| console.log("Search query:", query); | |||
| // Fix: Ensure completedJobOrderPickOrders is an array before filtering | |||
| if (!Array.isArray(completedJobOrderPickOrders)) { | |||
| setFilteredJobOrderPickOrders([]); | |||
| return; | |||
| } | |||
| const filtered = completedJobOrderPickOrders.filter((pickOrder) => { | |||
| const pickOrderCodeMatch = !query.pickOrderCode || | |||
| pickOrder.pickOrderCode?.toLowerCase().includes((query.pickOrderCode || "").toLowerCase()); | |||
| const jobOrderCodeMatch = !query.jobOrderCode || | |||
| pickOrder.jobOrderCode?.toLowerCase().includes((query.jobOrderCode || "").toLowerCase()); | |||
| const jobOrderNameMatch = !query.jobOrderName || | |||
| pickOrder.jobOrderName?.toLowerCase().includes((query.jobOrderName || "").toLowerCase()); | |||
| return pickOrderCodeMatch && jobOrderCodeMatch && jobOrderNameMatch; | |||
| }); | |||
| setFilteredJobOrderPickOrders(filtered); | |||
| console.log("Filtered Job Order pick orders count:", filtered.length); | |||
| }, [completedJobOrderPickOrders]); | |||
| }, []); | |||
| const formatDateTime = (value: any) => { | |||
| if (!value) return "-"; | |||
| @@ -233,12 +212,57 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ | |||
| const d = new Date(value); | |||
| return isNaN(d.getTime()) ? "-" : d.toLocaleString(); | |||
| }; | |||
| const toDateYMD = (value: any): string | null => { | |||
| if (!value) return null; | |||
| // Backend 常见格式:[yyyy, MM, dd, HH, mm, ss] | |||
| if (Array.isArray(value)) { | |||
| const [year, month, day] = value; | |||
| if (!year || !month || !day) return null; | |||
| return dayjs(new Date(Number(year), Number(month) - 1, Number(day))).format("YYYY-MM-DD"); | |||
| } | |||
| // 兼容 ISO 字符串 / Date | |||
| if (typeof value === "string") { | |||
| const d = dayjs(value); | |||
| return d.isValid() ? d.format("YYYY-MM-DD") : null; | |||
| } | |||
| if (value instanceof Date) { | |||
| return dayjs(value).format("YYYY-MM-DD"); | |||
| } | |||
| return null; | |||
| }; | |||
| useEffect(() => { | |||
| const query = searchQuery || {}; | |||
| const dateFilter = query.completedDate ? String(query.completedDate) : ""; | |||
| const filtered = (Array.isArray(completedJobOrderPickOrders) ? completedJobOrderPickOrders : []).filter((pickOrder) => { | |||
| const pickOrderDateYMD = toDateYMD((pickOrder as any).completedDate ?? (pickOrder as any).planEnd); | |||
| const dateMatch = !dateFilter || pickOrderDateYMD === dateFilter; | |||
| const pickOrderCodeMatch = | |||
| !query.pickOrderCode || | |||
| pickOrder.pickOrderCode?.toLowerCase().includes(String(query.pickOrderCode).toLowerCase()); | |||
| const jobOrderCodeMatch = | |||
| !query.jobOrderCode || | |||
| pickOrder.jobOrderCode?.toLowerCase().includes(String(query.jobOrderCode).toLowerCase()); | |||
| const jobOrderNameMatch = | |||
| !query.jobOrderName || | |||
| pickOrder.jobOrderName?.toLowerCase().includes(String(query.jobOrderName).toLowerCase()); | |||
| return dateMatch && pickOrderCodeMatch && jobOrderCodeMatch && jobOrderNameMatch; | |||
| }); | |||
| setFilteredJobOrderPickOrders(filtered); | |||
| }, [completedJobOrderPickOrders, searchQuery]); | |||
| // 修改:重置搜索 | |||
| const handleSearchReset = useCallback(() => { | |||
| setSearchQuery({}); | |||
| // Fix: Ensure completedJobOrderPickOrders is an array before setting | |||
| setFilteredJobOrderPickOrders(Array.isArray(completedJobOrderPickOrders) ? completedJobOrderPickOrders : []); | |||
| }, [completedJobOrderPickOrders]); | |||
| }, []); | |||
| // 修改:分页功能 | |||
| const handlePageChange = useCallback((event: unknown, newPage: number) => { | |||
| @@ -270,6 +294,11 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ | |||
| // 修改:搜索条件 | |||
| const searchCriteria: Criterion<any>[] = [ | |||
| { | |||
| label: t("Target Date"), | |||
| paramName: "completedDate", | |||
| type: "date", | |||
| }, | |||
| { | |||
| label: t("Pick Order Code"), | |||
| paramName: "pickOrderCode", | |||
| @@ -17,16 +17,14 @@ import { | |||
| Paper, | |||
| IconButton, | |||
| Tooltip, | |||
| FormControl, | |||
| InputLabel, | |||
| Select, | |||
| MenuItem, | |||
| } from "@mui/material"; | |||
| import QrCodeIcon from '@mui/icons-material/QrCode'; | |||
| import { useTranslation } from "react-i18next"; | |||
| import { useSession } from "next-auth/react"; | |||
| import { SessionWithTokens } from "@/config/authConfig"; | |||
| import dayjs from "dayjs"; | |||
| import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers"; | |||
| import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; | |||
| import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | |||
| import { | |||
| fetchJoForPrintQrCode, | |||
| @@ -54,27 +52,12 @@ const FinishedQcJobOrderList: React.FC<FinishedQcJobOrderListProps> = ({ | |||
| const [page, setPage] = useState(0); | |||
| const [isPrinting, setIsPrinting] = useState(false); | |||
| const [printingId, setPrintingId] = useState<number | null>(null); | |||
| const [selectedDate, setSelectedDate] = useState<string>("today"); | |||
| const getDateLabel = (offset: number) => { | |||
| return dayjs().subtract(offset, 'day').format('YYYY-MM-DD'); | |||
| }; | |||
| // 根据选择的日期获取实际日期字符串 | |||
| const getDateParam = (dateOption: string): string => { | |||
| if (dateOption === "today") { | |||
| return dayjs().format('YYYY-MM-DD'); | |||
| } else if (dateOption === "yesterday") { | |||
| return dayjs().subtract(1, 'day').format('YYYY-MM-DD'); | |||
| } else if (dateOption === "dayBeforeYesterday") { | |||
| return dayjs().subtract(2, 'day').format('YYYY-MM-DD'); | |||
| } | |||
| return dayjs().format('YYYY-MM-DD'); | |||
| }; | |||
| const [selectedDate, setSelectedDate] = useState<dayjs.Dayjs>(dayjs()); | |||
| const fetchJobOrders = useCallback(async () => { | |||
| setLoading(true); | |||
| try { | |||
| const dateParam = getDateParam(selectedDate); | |||
| const dateParam = selectedDate.format("YYYY-MM-DD"); | |||
| const data = await fetchJoForPrintQrCode(dateParam); | |||
| setJobOrders(data || []); | |||
| setPage(0); | |||
| @@ -140,28 +123,19 @@ const FinishedQcJobOrderList: React.FC<FinishedQcJobOrderListProps> = ({ | |||
| {/* Date Selector */} | |||
| <Stack direction="row" spacing={2} sx={{ mb: 2, alignItems: 'flex-start' }}> | |||
| <Box sx={{ maxWidth: 300 }}> | |||
| <FormControl fullWidth size="small"> | |||
| <InputLabel id="date-select-label">{t("Select Date")}</InputLabel> | |||
| <Select | |||
| labelId="date-select-label" | |||
| id="date-select" | |||
| <LocalizationProvider dateAdapter={AdapterDayjs}> | |||
| <DatePicker | |||
| label={t("Select Date")} | |||
| value={selectedDate} | |||
| // label={t("Select Date")} | |||
| onChange={(e) => { | |||
| setSelectedDate(e.target.value); | |||
| onChange={(newValue) => { | |||
| if (newValue) setSelectedDate(newValue); | |||
| }} | |||
| format="YYYY-MM-DD" | |||
| slotProps={{ | |||
| textField: { fullWidth: true, size: "small" }, | |||
| }} | |||
| > | |||
| <MenuItem value="today"> | |||
| {t("Today")} ({getDateLabel(0)}) | |||
| </MenuItem> | |||
| <MenuItem value="yesterday"> | |||
| {t("Yesterday")} ({getDateLabel(1)}) | |||
| </MenuItem> | |||
| <MenuItem value="dayBeforeYesterday"> | |||
| {t("Day Before Yesterday")} ({getDateLabel(2)}) | |||
| </MenuItem> | |||
| </Select> | |||
| </FormControl> | |||
| /> | |||
| </LocalizationProvider> | |||
| </Box> | |||
| </Stack> | |||
| @@ -22,7 +22,7 @@ | |||
| "Duration (Minutes)": "時間(分)", | |||
| "Prep Time (Minutes)": "準備時間", | |||
| "Post Prod Time (Minutes)": "收尾時間", | |||
| "Correct BOM List (Can Import)": "正確 BOM 列表(可匯入)", | |||
| "Select Another Bag Lot": "選擇另一個包裝袋", | |||
| "Finished QC Job Orders": "完成QC工單", | |||