| @@ -15,15 +15,14 @@ import { | |||||
| Paper, | Paper, | ||||
| CircularProgress, | CircularProgress, | ||||
| TablePagination, | TablePagination, | ||||
| FormControl, | |||||
| Select, | |||||
| MenuItem, | |||||
| Stack | Stack | ||||
| } from '@mui/material'; | } from '@mui/material'; | ||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import dayjs from 'dayjs'; | import dayjs from 'dayjs'; | ||||
| import { arrayToDayjs } from '@/app/utils/formatUtil'; | import { arrayToDayjs } from '@/app/utils/formatUtil'; | ||||
| import { fetchMaterialPickStatus, MaterialPickStatusItem } from '@/app/api/jo/actions'; | 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 | const REFRESH_INTERVAL = 10 * 60 * 1000; // 10 minutes in milliseconds | ||||
| @@ -242,16 +241,19 @@ const MaterialPickStatusTable: React.FC = () => { | |||||
| {/* Filters */} | {/* Filters */} | ||||
| <Stack direction="row" spacing={2} sx={{ mb: 3 }}> | <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 }} /> | <Box sx={{ flexGrow: 1 }} /> | ||||
| <Stack direction="row" spacing={2} sx={{ alignSelf: 'center' }}> | <Stack direction="row" spacing={2} sx={{ alignSelf: 'center' }}> | ||||
| @@ -46,6 +46,7 @@ import { useSession } from "next-auth/react"; | |||||
| import { SessionWithTokens } from "@/config/authConfig"; | import { SessionWithTokens } from "@/config/authConfig"; | ||||
| import Swal from "sweetalert2"; | import Swal from "sweetalert2"; | ||||
| import { PrinterCombo } from "@/app/api/settings/printer"; | import { PrinterCombo } from "@/app/api/settings/printer"; | ||||
| import dayjs from "dayjs"; | |||||
| interface Props { | interface Props { | ||||
| filterArgs: Record<string, any>; | filterArgs: Record<string, any>; | ||||
| printerCombo: PrinterCombo[]; | printerCombo: PrinterCombo[]; | ||||
| @@ -61,14 +62,15 @@ interface CompletedJobOrderPickOrder { | |||||
| pickOrderConsoCode: string; | pickOrderConsoCode: string; | ||||
| pickOrderTargetDate: string; | pickOrderTargetDate: string; | ||||
| pickOrderStatus: string; | pickOrderStatus: string; | ||||
| completedDate: string; | |||||
| // Backend可能返回 [yyyy, MM, dd, HH, mm, ss] 或字符串,前端统一在 helper 里转成 YYYY-MM-DD | |||||
| completedDate: any; | |||||
| jobOrderId: number; | jobOrderId: number; | ||||
| jobOrderCode: string; | jobOrderCode: string; | ||||
| jobOrderName: string; | jobOrderName: string; | ||||
| reqQty: number; | reqQty: number; | ||||
| uom: string; | uom: string; | ||||
| planStart: string; | |||||
| planEnd: string; | |||||
| planStart: any; | |||||
| planEnd: any; | |||||
| secondScanCompleted: boolean; | secondScanCompleted: boolean; | ||||
| totalItems: number; | totalItems: number; | ||||
| completedItems: number; | completedItems: number; | ||||
| @@ -193,33 +195,10 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ | |||||
| } | } | ||||
| }, [currentUserId, fetchCompletedJobOrderPickOrdersData]); | }, [currentUserId, fetchCompletedJobOrderPickOrdersData]); | ||||
| // 修改:搜索功能 | |||||
| // 修改:搜索功能(只更新 query;实际过滤交给 useEffect + date filter 统一处理) | |||||
| const handleSearch = useCallback((query: Record<string, any>) => { | const handleSearch = useCallback((query: Record<string, any>) => { | ||||
| setSearchQuery({ ...query }); | 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) => { | const formatDateTime = (value: any) => { | ||||
| if (!value) return "-"; | if (!value) return "-"; | ||||
| @@ -233,12 +212,57 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ | |||||
| const d = new Date(value); | const d = new Date(value); | ||||
| return isNaN(d.getTime()) ? "-" : d.toLocaleString(); | 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(() => { | const handleSearchReset = useCallback(() => { | ||||
| setSearchQuery({}); | setSearchQuery({}); | ||||
| // Fix: Ensure completedJobOrderPickOrders is an array before setting | |||||
| setFilteredJobOrderPickOrders(Array.isArray(completedJobOrderPickOrders) ? completedJobOrderPickOrders : []); | |||||
| }, [completedJobOrderPickOrders]); | |||||
| }, []); | |||||
| // 修改:分页功能 | // 修改:分页功能 | ||||
| const handlePageChange = useCallback((event: unknown, newPage: number) => { | const handlePageChange = useCallback((event: unknown, newPage: number) => { | ||||
| @@ -270,6 +294,11 @@ const CompleteJobOrderRecord: React.FC<Props> = ({ | |||||
| // 修改:搜索条件 | // 修改:搜索条件 | ||||
| const searchCriteria: Criterion<any>[] = [ | const searchCriteria: Criterion<any>[] = [ | ||||
| { | |||||
| label: t("Target Date"), | |||||
| paramName: "completedDate", | |||||
| type: "date", | |||||
| }, | |||||
| { | { | ||||
| label: t("Pick Order Code"), | label: t("Pick Order Code"), | ||||
| paramName: "pickOrderCode", | paramName: "pickOrderCode", | ||||
| @@ -17,16 +17,14 @@ import { | |||||
| Paper, | Paper, | ||||
| IconButton, | IconButton, | ||||
| Tooltip, | Tooltip, | ||||
| FormControl, | |||||
| InputLabel, | |||||
| Select, | |||||
| MenuItem, | |||||
| } from "@mui/material"; | } from "@mui/material"; | ||||
| import QrCodeIcon from '@mui/icons-material/QrCode'; | import QrCodeIcon from '@mui/icons-material/QrCode'; | ||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import { useSession } from "next-auth/react"; | import { useSession } from "next-auth/react"; | ||||
| import { SessionWithTokens } from "@/config/authConfig"; | import { SessionWithTokens } from "@/config/authConfig"; | ||||
| import dayjs from "dayjs"; | 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 { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | ||||
| import { | import { | ||||
| fetchJoForPrintQrCode, | fetchJoForPrintQrCode, | ||||
| @@ -54,27 +52,12 @@ const FinishedQcJobOrderList: React.FC<FinishedQcJobOrderListProps> = ({ | |||||
| const [page, setPage] = useState(0); | const [page, setPage] = useState(0); | ||||
| const [isPrinting, setIsPrinting] = useState(false); | const [isPrinting, setIsPrinting] = useState(false); | ||||
| const [printingId, setPrintingId] = useState<number | null>(null); | 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 () => { | const fetchJobOrders = useCallback(async () => { | ||||
| setLoading(true); | setLoading(true); | ||||
| try { | try { | ||||
| const dateParam = getDateParam(selectedDate); | |||||
| const dateParam = selectedDate.format("YYYY-MM-DD"); | |||||
| const data = await fetchJoForPrintQrCode(dateParam); | const data = await fetchJoForPrintQrCode(dateParam); | ||||
| setJobOrders(data || []); | setJobOrders(data || []); | ||||
| setPage(0); | setPage(0); | ||||
| @@ -140,28 +123,19 @@ const FinishedQcJobOrderList: React.FC<FinishedQcJobOrderListProps> = ({ | |||||
| {/* Date Selector */} | {/* Date Selector */} | ||||
| <Stack direction="row" spacing={2} sx={{ mb: 2, alignItems: 'flex-start' }}> | <Stack direction="row" spacing={2} sx={{ mb: 2, alignItems: 'flex-start' }}> | ||||
| <Box sx={{ maxWidth: 300 }}> | <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} | 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> | </Box> | ||||
| </Stack> | </Stack> | ||||
| @@ -22,7 +22,7 @@ | |||||
| "Duration (Minutes)": "時間(分)", | "Duration (Minutes)": "時間(分)", | ||||
| "Prep Time (Minutes)": "準備時間", | "Prep Time (Minutes)": "準備時間", | ||||
| "Post Prod Time (Minutes)": "收尾時間", | "Post Prod Time (Minutes)": "收尾時間", | ||||
| "Correct BOM List (Can Import)": "正確 BOM 列表(可匯入)", | "Correct BOM List (Can Import)": "正確 BOM 列表(可匯入)", | ||||
| "Select Another Bag Lot": "選擇另一個包裝袋", | "Select Another Bag Lot": "選擇另一個包裝袋", | ||||
| "Finished QC Job Orders": "完成QC工單", | "Finished QC Job Orders": "完成QC工單", | ||||