FPSMS-frontend
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 

822 строки
29 KiB

  1. "use client";
  2. import {
  3. Box,
  4. Button,
  5. Stack,
  6. TextField,
  7. Typography,
  8. Alert,
  9. CircularProgress,
  10. Table,
  11. TableBody,
  12. TableCell,
  13. TableContainer,
  14. TableHead,
  15. TableRow,
  16. Paper,
  17. TablePagination,
  18. Modal,
  19. Card,
  20. CardContent,
  21. CardActions,
  22. Chip,
  23. Accordion,
  24. AccordionSummary,
  25. AccordionDetails,
  26. Checkbox,
  27. Autocomplete,
  28. } from "@mui/material";
  29. import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
  30. import { useCallback, useEffect, useState, useRef, useMemo } from "react";
  31. import { useTranslation } from "react-i18next";
  32. import { useRouter } from "next/navigation";
  33. import {
  34. fetchJobOrderPickOrdersrecords,
  35. fetchJobOrderPickOrderLotDetailsForPick,
  36. PrintPickRecord
  37. } from "@/app/api/jo/actions";
  38. import { fetchNameList, NameList } from "@/app/api/user/actions";
  39. import {
  40. FormProvider,
  41. useForm,
  42. } from "react-hook-form";
  43. import SearchBox, { Criterion } from "../SearchBox";
  44. import { useSession } from "next-auth/react";
  45. import { SessionWithTokens } from "@/config/authConfig";
  46. import Swal from "sweetalert2";
  47. import { PrinterCombo } from "@/app/api/settings/printer";
  48. import {
  49. buildPrintPickRecordRequest,
  50. promptAllFloorsPlasticBoxCartonQty,
  51. promptPlasticBoxCartonQty,
  52. type PickRecordFloor,
  53. } from "./pickRecordHelpers";
  54. import dayjs from "dayjs";
  55. interface Props {
  56. filterArgs: Record<string, any>;
  57. printerCombo: PrinterCombo[];
  58. selectedPrinter?: PrinterCombo | null;
  59. printQty?: number;
  60. initialPickOrderCode?: string;
  61. initialTargetDate?: string;
  62. /** When true (Item Tracing `openDetail=1`), auto-open matching pick-order detail. */
  63. openDetail?: boolean;
  64. }
  65. // 修改:已完成的 Job Order Pick Order 接口
  66. interface CompletedJobOrderPickOrder {
  67. id: number;
  68. pickOrderId: number;
  69. pickOrderCode: string;
  70. pickOrderConsoCode: string;
  71. pickOrderTargetDate: string;
  72. pickOrderStatus: string;
  73. // Backend可能返回 [yyyy, MM, dd, HH, mm, ss] 或字符串,前端统一在 helper 里转成 YYYY-MM-DD
  74. completedDate: any;
  75. jobOrderId: number;
  76. jobOrderCode: string;
  77. jobOrderName: string;
  78. reqQty: number;
  79. uom: string;
  80. planStart: any;
  81. planEnd: any;
  82. secondScanCompleted: boolean;
  83. totalItems: number;
  84. completedItems: number;
  85. }
  86. // 新增:Lot 详情接口
  87. interface LotDetail {
  88. lotId: number | null;
  89. lotNo: string | null;
  90. expiryDate: string | null;
  91. location: string | null;
  92. availableQty: number | null;
  93. requiredQty: number | null;
  94. actualPickQty: number | null;
  95. processingStatus: string;
  96. lotAvailability: string;
  97. pickOrderId: number;
  98. pickOrderCode: string;
  99. pickOrderConsoCode: string;
  100. pickOrderLineId: number;
  101. stockOutLineId: number | null;
  102. stockOutLineStatus: string;
  103. routerIndex: number | null;
  104. routerArea: string | null;
  105. routerRoute: string | null;
  106. uomShortDesc: string | null;
  107. secondQrScanStatus: string;
  108. itemId: number;
  109. itemCode: string;
  110. itemName: string;
  111. uomCode: string;
  112. uomDesc: string;
  113. match_status: string | null;
  114. }
  115. const CompleteJobOrderRecord: React.FC<Props> = ({
  116. filterArgs,
  117. printerCombo,
  118. selectedPrinter: selectedPrinterProp,
  119. printQty: printQtyProp,
  120. initialPickOrderCode,
  121. initialTargetDate,
  122. openDetail = false,
  123. }) => {
  124. const { t } = useTranslation("jo");
  125. const router = useRouter();
  126. const { data: session } = useSession() as { data: SessionWithTokens | null };
  127. const currentUserId = session?.id ? parseInt(session.id) : undefined;
  128. // 修改:已完成 Job Order Pick Orders 状态
  129. const [completedJobOrderPickOrders, setCompletedJobOrderPickOrders] = useState<CompletedJobOrderPickOrder[]>([]);
  130. const [completedJobOrderPickOrdersLoading, setCompletedJobOrderPickOrdersLoading] = useState(false);
  131. // 修改:详情视图状态
  132. const [selectedJobOrderPickOrder, setSelectedJobOrderPickOrder] = useState<CompletedJobOrderPickOrder | null>(null);
  133. const [showDetailView, setShowDetailView] = useState(false);
  134. const [detailLotData, setDetailLotData] = useState<LotDetail[]>([]);
  135. const [detailLotDataLoading, setDetailLotDataLoading] = useState(false);
  136. // 修改:搜索状态
  137. const [searchQuery, setSearchQuery] = useState<Record<string, any>>(() => {
  138. const raw = initialTargetDate?.trim() || "";
  139. const match = raw.match(/(\d{4}-\d{2}-\d{2})/);
  140. return {
  141. completedDate: match?.[1] || dayjs().format("YYYY-MM-DD"),
  142. ...(initialPickOrderCode?.trim()
  143. ? { pickOrderCode: initialPickOrderCode.trim() }
  144. : {}),
  145. };
  146. });
  147. const [filteredJobOrderPickOrders, setFilteredJobOrderPickOrders] = useState<CompletedJobOrderPickOrder[]>([]);
  148. // Use props with fallback
  149. const selectedPrinter = selectedPrinterProp ?? (printerCombo && printerCombo.length > 0 ? printerCombo[0] : null);
  150. const printQty = printQtyProp ?? 1;
  151. const pickRecordPrintInFlightRef = useRef(false);
  152. const initialDetailOpenedRef = useRef(false);
  153. // 修改:分页状态
  154. const [paginationController, setPaginationController] = useState({
  155. pageNum: 0,
  156. pageSize: 10,
  157. });
  158. const formProps = useForm();
  159. const errors = formProps.formState.errors;
  160. // 修改:使用新的 Job Order API 获取已完成的 Job Order Pick Orders(仅完成pick的)
  161. const fetchJobOrderPickOrdersData = useCallback(
  162. async (forDate?: string, forStatus?: string) => {
  163. if (!currentUserId) return;
  164. setCompletedJobOrderPickOrdersLoading(true);
  165. try {
  166. const dateParam =
  167. forDate !== undefined
  168. ? forDate
  169. : searchQuery.completedDate
  170. ? String(searchQuery.completedDate)
  171. : dayjs().format("YYYY-MM-DD");
  172. const statusParam =
  173. forStatus !== undefined
  174. ? forStatus
  175. : searchQuery.pickOrderStatus
  176. ? String(searchQuery.pickOrderStatus)
  177. : null;
  178. const data = await fetchJobOrderPickOrdersrecords(
  179. dateParam?.trim() ? dateParam.trim() : null,
  180. statusParam?.trim() ? statusParam.trim() : null,
  181. );
  182. const safeData = Array.isArray(data) ? data : [];
  183. setCompletedJobOrderPickOrders(safeData);
  184. setFilteredJobOrderPickOrders(safeData);
  185. } finally {
  186. setCompletedJobOrderPickOrdersLoading(false);
  187. }
  188. },
  189. [currentUserId, searchQuery.completedDate, searchQuery.pickOrderStatus],
  190. );
  191. // 新增:获取 lot 详情数据(使用新的API)
  192. const fetchLotDetailsData = useCallback(async (pickOrderId: number) => {
  193. setDetailLotDataLoading(true);
  194. try {
  195. console.log("🔍 Fetching lot details for completed pick order:", pickOrderId);
  196. const lotDetails = await fetchJobOrderPickOrderLotDetailsForPick(pickOrderId);
  197. setDetailLotData(Array.isArray(lotDetails) ? lotDetails : []);
  198. console.log(" Fetched lot details:", lotDetails);
  199. } catch (error) {
  200. console.error("❌ Error fetching lot details:", error);
  201. setDetailLotData([]);
  202. } finally {
  203. setDetailLotDataLoading(false);
  204. }
  205. }, []);
  206. // 修改:初始化时获取数据
  207. useEffect(() => {
  208. if (!currentUserId) return;
  209. const d = searchQuery?.completedDate;
  210. const s = searchQuery?.pickOrderStatus;
  211. const dateStr = d != null && String(d).trim() !== "" ? String(d).trim() : "";
  212. const statusStr = s != null && String(s).trim() !== "" ? String(s).trim() : "";
  213. void fetchJobOrderPickOrdersData(dateStr || undefined, statusStr || undefined);
  214. }, [currentUserId, searchQuery?.completedDate, searchQuery?.pickOrderStatus, fetchJobOrderPickOrdersData]);
  215. // 修改:搜索功能(只更新 query;实际过滤交给 useEffect + date filter 统一处理)
  216. const handleSearch = useCallback((query: Record<string, any>) => {
  217. setSearchQuery({ ...query });
  218. }, []);
  219. const formatDateTime = (value: any) => {
  220. if (!value) return "-";
  221. // 后端发来的是 [yyyy, MM, dd, HH, mm, ss]
  222. if (Array.isArray(value)) {
  223. const [year, month, day, hour = 0, minute = 0, second = 0] = value;
  224. return new Date(year, month - 1, day, hour, minute, second).toLocaleString();
  225. }
  226. // 如果以后改成字符串/ISO,也兼容
  227. const d = new Date(value);
  228. return isNaN(d.getTime()) ? "-" : d.toLocaleString();
  229. };
  230. const toDateYMD = (value: any): string | null => {
  231. if (!value) return null;
  232. // Backend 常见格式:[yyyy, MM, dd, HH, mm, ss]
  233. if (Array.isArray(value)) {
  234. const [year, month, day] = value;
  235. if (!year || !month || !day) return null;
  236. return dayjs(new Date(Number(year), Number(month) - 1, Number(day))).format("YYYY-MM-DD");
  237. }
  238. // 兼容 ISO 字符串 / Date
  239. if (typeof value === "string") {
  240. const d = dayjs(value);
  241. return d.isValid() ? d.format("YYYY-MM-DD") : null;
  242. }
  243. if (value instanceof Date) {
  244. return dayjs(value).format("YYYY-MM-DD");
  245. }
  246. return null;
  247. };
  248. useEffect(() => {
  249. const query = searchQuery || {};
  250. const dateFilter = query.completedDate ? String(query.completedDate) : "";
  251. const filtered = (Array.isArray(completedJobOrderPickOrders) ? completedJobOrderPickOrders : []).filter((pickOrder) => {
  252. const pickOrderDateYMD = toDateYMD((pickOrder as any).completedDate ?? (pickOrder as any).planEnd);
  253. const dateMatch = !dateFilter || pickOrderDateYMD === dateFilter;
  254. const pickOrderCodeMatch =
  255. !query.pickOrderCode ||
  256. pickOrder.pickOrderCode?.toLowerCase().includes(String(query.pickOrderCode).toLowerCase());
  257. const jobOrderCodeMatch =
  258. !query.jobOrderCode ||
  259. pickOrder.jobOrderCode?.toLowerCase().includes(String(query.jobOrderCode).toLowerCase());
  260. const jobOrderNameMatch =
  261. !query.jobOrderName ||
  262. pickOrder.jobOrderName?.toLowerCase().includes(String(query.jobOrderName).toLowerCase());
  263. return dateMatch && pickOrderCodeMatch && jobOrderCodeMatch && jobOrderNameMatch;
  264. });
  265. setFilteredJobOrderPickOrders(filtered);
  266. }, [completedJobOrderPickOrders, searchQuery]);
  267. // 修改:重置搜索
  268. const handleSearchReset = useCallback(() => {
  269. setSearchQuery({});
  270. }, []);
  271. // 修改:分页功能
  272. const handlePageChange = useCallback((event: unknown, newPage: number) => {
  273. setPaginationController(prev => ({
  274. ...prev,
  275. pageNum: newPage,
  276. }));
  277. }, []);
  278. const handlePageSizeChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {
  279. const newPageSize = parseInt(event.target.value, 10);
  280. setPaginationController({
  281. pageNum: 0,
  282. pageSize: newPageSize,
  283. });
  284. }, []);
  285. // 修改:分页数据
  286. const paginatedData = useMemo(() => {
  287. // Fix: Ensure filteredJobOrderPickOrders is an array before calling slice
  288. if (!Array.isArray(filteredJobOrderPickOrders)) {
  289. return [];
  290. }
  291. const startIndex = paginationController.pageNum * paginationController.pageSize;
  292. const endIndex = startIndex + paginationController.pageSize;
  293. return filteredJobOrderPickOrders.slice(startIndex, endIndex);
  294. }, [filteredJobOrderPickOrders, paginationController]);
  295. // 修改:搜索条件
  296. const searchCriteria: Criterion<any>[] = [
  297. {
  298. label: t("Target Date"),
  299. paramName: "completedDate",
  300. type: "date",
  301. },
  302. {
  303. label: t("Pick Order Code"),
  304. paramName: "pickOrderCode",
  305. type: "text",
  306. },
  307. {
  308. label: t("Job Order Code"),
  309. paramName: "jobOrderCode",
  310. type: "text",
  311. },
  312. {
  313. label: t("Pick Order Status"),
  314. paramName: "pickOrderStatus",
  315. type: "select-labelled",
  316. options: [
  317. { label: t("Released"), value: "RELEASED" },
  318. { label: t("Completed"), value: "COMPLETED" },
  319. ], // 依你后端实际枚举
  320. },
  321. {
  322. label: t("Job Order Item Name"),
  323. paramName: "jobOrderName",
  324. type: "text",
  325. }
  326. ];
  327. // 修改:详情点击处理
  328. const handleDetailClick = useCallback(async (jobOrderPickOrder: CompletedJobOrderPickOrder) => {
  329. setSelectedJobOrderPickOrder(jobOrderPickOrder);
  330. setShowDetailView(true);
  331. // 获取 lot 详情数据(使用新的API)
  332. await fetchLotDetailsData(jobOrderPickOrder.pickOrderId);
  333. // 触发打印按钮状态更新 - 基于详情数据
  334. const allCompleted = jobOrderPickOrder.secondScanCompleted;
  335. // 发送事件,包含标签页信息
  336. window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', {
  337. detail: {
  338. allLotsCompleted: allCompleted,
  339. tabIndex: 3 // 明确指定这是来自标签页 3 的事件
  340. }
  341. }));
  342. }, [fetchLotDetailsData]);
  343. useEffect(() => {
  344. if (!openDetail) return;
  345. if (initialDetailOpenedRef.current || completedJobOrderPickOrdersLoading) return;
  346. const code = initialPickOrderCode?.trim();
  347. if (!code) return;
  348. const match = completedJobOrderPickOrders.find(
  349. (row) =>
  350. row.pickOrderCode?.trim() === code ||
  351. row.pickOrderConsoCode?.trim() === code,
  352. );
  353. if (!match) return;
  354. initialDetailOpenedRef.current = true;
  355. void handleDetailClick(match);
  356. }, [
  357. openDetail,
  358. completedJobOrderPickOrders,
  359. completedJobOrderPickOrdersLoading,
  360. initialPickOrderCode,
  361. handleDetailClick,
  362. ]);
  363. // 修改:返回列表视图
  364. const handleBackToList = useCallback(() => {
  365. setShowDetailView(false);
  366. setSelectedJobOrderPickOrder(null);
  367. setDetailLotData([]);
  368. // 返回列表时禁用打印按钮
  369. window.dispatchEvent(new CustomEvent('pickOrderCompletionStatus', {
  370. detail: {
  371. allLotsCompleted: false,
  372. tabIndex: 3
  373. }
  374. }));
  375. }, []);
  376. const handlePickRecord = useCallback(async (
  377. jobOrderPickOrder: CompletedJobOrderPickOrder,
  378. floor: PickRecordFloor,
  379. ) => {
  380. if (pickRecordPrintInFlightRef.current) return;
  381. try {
  382. if (!jobOrderPickOrder) {
  383. console.error("No selected job order pick order available");
  384. return;
  385. }
  386. if (!selectedPrinter) {
  387. Swal.fire({
  388. position: "bottom-end",
  389. icon: "warning",
  390. text: t("Please select a printer first"),
  391. showConfirmButton: false,
  392. timer: 1500
  393. });
  394. return;
  395. }
  396. if (!printQty || printQty < 1) {
  397. Swal.fire({
  398. position: "bottom-end",
  399. icon: "warning",
  400. text: t("Please enter a valid print quantity (at least 1)"),
  401. showConfirmButton: false,
  402. timer: 1500
  403. });
  404. return;
  405. }
  406. const pickOrderId = jobOrderPickOrder.pickOrderId;
  407. let printRequest;
  408. if (floor === "ALL") {
  409. const allFloorsQty = await promptAllFloorsPlasticBoxCartonQty(t, pickOrderId);
  410. if (allFloorsQty === null) {
  411. return;
  412. }
  413. printRequest = buildPrintPickRecordRequest({
  414. pickOrderId,
  415. printerId: selectedPrinter.id,
  416. printQty,
  417. floor,
  418. allFloorsQty,
  419. });
  420. } else {
  421. const plasticBoxCartonQty = await promptPlasticBoxCartonQty(t);
  422. if (plasticBoxCartonQty === null) {
  423. return;
  424. }
  425. printRequest = buildPrintPickRecordRequest({
  426. pickOrderId,
  427. printerId: selectedPrinter.id,
  428. printQty,
  429. floor,
  430. plasticBoxCartonQty,
  431. });
  432. }
  433. pickRecordPrintInFlightRef.current = true;
  434. const response = await PrintPickRecord(printRequest);
  435. if (response.success) {
  436. Swal.fire({
  437. position: "bottom-end",
  438. icon: "success",
  439. text: t("Printed Successfully."),
  440. showConfirmButton: false,
  441. timer: 1500
  442. });
  443. } else {
  444. Swal.fire({
  445. position: "bottom-end",
  446. icon: "error",
  447. text: response.message || t("Print failed"),
  448. showConfirmButton: false,
  449. timer: 1500
  450. });
  451. }
  452. } catch (error) {
  453. console.error("error: ", error);
  454. Swal.fire({
  455. position: "bottom-end",
  456. icon: "error",
  457. text: t("An error occurred while printing"),
  458. showConfirmButton: false,
  459. timer: 1500
  460. });
  461. } finally {
  462. pickRecordPrintInFlightRef.current = false;
  463. }
  464. }, [t, selectedPrinter, printQty]);
  465. // 修改:如果显示详情视图,渲染 Job Order 详情和 Lot 信息
  466. if (showDetailView && selectedJobOrderPickOrder) {
  467. return (
  468. <FormProvider {...formProps}>
  469. <Box>
  470. {/* 返回按钮和标题 */}
  471. <Box sx={{ mb: 2, display: 'flex', alignItems: 'center', gap: 2 }}>
  472. <Button variant="outlined" onClick={handleBackToList}>
  473. {t("Back to List")}
  474. </Button>
  475. <Typography variant="h6">
  476. {t("Job Order Pick Order Details")}: {selectedJobOrderPickOrder.pickOrderCode}
  477. </Typography>
  478. </Box>
  479. {/* Job Order 信息卡片 */}
  480. <Card sx={{ mb: 2 }}>
  481. <CardContent>
  482. <Stack direction="row" spacing={4} useFlexGap flexWrap="wrap">
  483. <Typography variant="subtitle1">
  484. <strong>{t("Pick Order Code")}:</strong> {selectedJobOrderPickOrder.pickOrderCode}
  485. </Typography>
  486. <Typography variant="subtitle1">
  487. <strong>{t("Job Order Code")}:</strong> {selectedJobOrderPickOrder.jobOrderCode}
  488. </Typography>
  489. <Typography variant="subtitle1">
  490. <strong>{t("Job Order Item Name")}:</strong> {selectedJobOrderPickOrder.jobOrderName}
  491. </Typography>
  492. <Typography variant="subtitle1">
  493. <strong>{t("Target Date")}:</strong> {selectedJobOrderPickOrder.pickOrderTargetDate}
  494. </Typography>
  495. </Stack>
  496. <Stack direction="row" spacing={4} useFlexGap flexWrap="wrap" sx={{ mt: 2 }}>
  497. <Typography variant="subtitle1">
  498. <strong>{t("Required Qty")}:</strong> {selectedJobOrderPickOrder.reqQty} {selectedJobOrderPickOrder.uom}
  499. </Typography>
  500. </Stack>
  501. </CardContent>
  502. </Card>
  503. {/* 修改:Lot 详情表格 - 添加复选框列 */}
  504. <Card>
  505. <CardContent>
  506. <Typography variant="h6" gutterBottom>
  507. {t("Lot Details")}
  508. </Typography>
  509. {detailLotDataLoading ? (
  510. <Box sx={{ display: 'flex', justifyContent: 'center', p: 2 }}>
  511. <CircularProgress />
  512. </Box>
  513. ) : (
  514. <TableContainer component={Paper}>
  515. <Table>
  516. <TableHead>
  517. <TableRow>
  518. <TableCell>{t("Index")}</TableCell>
  519. <TableCell>{t("Route")}</TableCell>
  520. <TableCell>{t("Item Code")}</TableCell>
  521. <TableCell>{t("Item Name")}</TableCell>
  522. <TableCell>{t("Lot No")}</TableCell>
  523. <TableCell align="right">{t("Required Qty")}</TableCell>
  524. <TableCell align="right">{t("Actual Pick Qty")}</TableCell>
  525. <TableCell align="center">{t("Processing Status")}</TableCell>
  526. <TableCell align="center">{t("Second Scan Status")}</TableCell>
  527. </TableRow>
  528. </TableHead>
  529. <TableBody>
  530. {detailLotData.length === 0 ? (
  531. <TableRow>
  532. <TableCell colSpan={10} align="center">
  533. <Typography variant="body2" color="text.secondary">
  534. {t("No lot details available")}
  535. </Typography>
  536. </TableCell>
  537. </TableRow>
  538. ) : (
  539. detailLotData.map((lot, index) => (
  540. <TableRow key={lot.stockOutLineId ?? lot.lotId ?? index}>
  541. <TableCell>
  542. <Typography variant="body2" fontWeight="bold">
  543. {index + 1}
  544. </Typography>
  545. </TableCell>
  546. <TableCell>
  547. <Typography variant="body2">
  548. {lot.routerRoute || '-'}
  549. </Typography>
  550. </TableCell>
  551. <TableCell>{lot.itemCode}</TableCell>
  552. <TableCell>{lot.itemName}</TableCell>
  553. <TableCell>{lot.lotNo || '-'}</TableCell>
  554. <TableCell align="right">
  555. {lot.requiredQty?.toLocaleString() || 0} ({lot.uomShortDesc || ''})
  556. </TableCell>
  557. <TableCell align="right">
  558. {lot.actualPickQty?.toLocaleString() || 0} ({lot.uomShortDesc || ''})
  559. </TableCell>
  560. {/* 修改:Processing Status 使用复选框 */}
  561. <TableCell align="center">
  562. <Box sx={{
  563. display: 'flex',
  564. justifyContent: 'center',
  565. alignItems: 'center',
  566. width: '100%',
  567. height: '100%'
  568. }}>
  569. <Checkbox
  570. checked={lot.processingStatus === 'completed'}
  571. disabled={true}
  572. readOnly={true}
  573. size="large"
  574. sx={{
  575. color: lot.processingStatus === 'completed' ? 'success.main' : 'grey.400',
  576. '&.Mui-checked': {
  577. color: 'success.main',
  578. },
  579. transform: 'scale(1.3)',
  580. '& .MuiSvgIcon-root': {
  581. fontSize: '1.5rem',
  582. }
  583. }}
  584. />
  585. </Box>
  586. </TableCell>
  587. {/* 修改:Second Scan Status 使用复选框 */}
  588. <TableCell align="center">
  589. <Box sx={{
  590. display: 'flex',
  591. justifyContent: 'center',
  592. alignItems: 'center',
  593. width: '100%',
  594. height: '100%'
  595. }}>
  596. <Checkbox
  597. checked={lot.match_status === 'completed'}
  598. disabled={true}
  599. readOnly={true}
  600. size="large"
  601. sx={{
  602. color: lot.match_status === 'completed' ? 'success.main' : 'grey.400',
  603. '&.Mui-checked': {
  604. color: 'success.main',
  605. },
  606. transform: 'scale(1.3)',
  607. '& .MuiSvgIcon-root': {
  608. fontSize: '1.5rem',
  609. }
  610. }}
  611. />
  612. </Box>
  613. </TableCell>
  614. </TableRow>
  615. ))
  616. )}
  617. </TableBody>
  618. </Table>
  619. </TableContainer>
  620. )}
  621. </CardContent>
  622. </Card>
  623. </Box>
  624. </FormProvider>
  625. );
  626. }
  627. // 修改:默认列表视图
  628. return (
  629. <FormProvider {...formProps}>
  630. <Box>
  631. {/* 搜索框 */}
  632. <Box sx={{ mb: 2 }}>
  633. <SearchBox
  634. criteria={searchCriteria}
  635. onSearch={handleSearch}
  636. onReset={handleSearchReset}
  637. />
  638. </Box>
  639. {/* 加载状态 */}
  640. {completedJobOrderPickOrdersLoading ? (
  641. <Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
  642. <CircularProgress />
  643. </Box>
  644. ) : (
  645. <Box>
  646. {/* 结果统计 */}
  647. <Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
  648. {t("Total")}: {filteredJobOrderPickOrders.length} {t("completed Job Order pick orders with matching")}
  649. </Typography>
  650. {/* 列表 */}
  651. {filteredJobOrderPickOrders.length === 0 ? (
  652. <Box sx={{ p: 3, textAlign: 'center' }}>
  653. <Typography variant="body2" color="text.secondary">
  654. {t("No completed Job Order pick orders with matching found")}
  655. </Typography>
  656. </Box>
  657. ) : (
  658. <Stack spacing={2}>
  659. {paginatedData.map((jobOrderPickOrder) => {
  660. const normalizedStatus = String(jobOrderPickOrder.pickOrderStatus ?? "").toLowerCase();
  661. return (
  662. <Card key={jobOrderPickOrder.id}>
  663. <CardContent>
  664. <Stack direction="row" justifyContent="space-between" alignItems="center">
  665. <Box>
  666. <Typography variant="h6">
  667. {jobOrderPickOrder.jobOrderCode}
  668. </Typography>
  669. <Typography variant="body2" color="text.secondary">
  670. {jobOrderPickOrder.jobOrderName} - {jobOrderPickOrder.pickOrderCode}
  671. </Typography>
  672. <Typography variant="body2" color="text.secondary">
  673. {t("Completed")}: {formatDateTime(jobOrderPickOrder.planEnd)}
  674. </Typography>
  675. <Typography variant="body2" color="text.secondary">
  676. {t("Target Date")}: {jobOrderPickOrder.pickOrderTargetDate}
  677. </Typography>
  678. </Box>
  679. <Box>
  680. <Chip
  681. label={t(jobOrderPickOrder.pickOrderStatus)}
  682. color={normalizedStatus === "completed" ? "success" : "default"}
  683. size="small"
  684. sx={{ mb: 1 }}
  685. />
  686. <Typography variant="body2" color="text.secondary">
  687. {jobOrderPickOrder.completedItems}/{jobOrderPickOrder.totalItems} {t("items completed")}
  688. </Typography>
  689. </Box>
  690. </Stack>
  691. </CardContent>
  692. <CardActions sx={{ alignItems: "center", gap: 1, flexWrap: "wrap" }}>
  693. <Button
  694. variant="outlined"
  695. onClick={() => handleDetailClick(jobOrderPickOrder)}
  696. >
  697. {t("View Details")}
  698. </Button>
  699. <Button
  700. variant="contained"
  701. color="primary"
  702. onClick={() => handlePickRecord(jobOrderPickOrder, "ALL")}
  703. >
  704. 打印全部樓層板頭紙
  705. </Button>
  706. <Button
  707. variant="contained"
  708. color="primary"
  709. onClick={() => handlePickRecord(jobOrderPickOrder, "2F")}
  710. >
  711. {t("Print Pick Record")} 2F
  712. </Button>
  713. <Button
  714. variant="contained"
  715. color="primary"
  716. onClick={() => handlePickRecord(jobOrderPickOrder, "3F")}
  717. >
  718. {t("Print Pick Record")} 3F
  719. </Button>
  720. <Button
  721. variant="contained"
  722. color="primary"
  723. onClick={() => handlePickRecord(jobOrderPickOrder, "4F")}
  724. >
  725. {t("Print Pick Record")} 4F
  726. </Button>
  727. </CardActions>
  728. </Card>
  729. );
  730. })}
  731. </Stack>
  732. )}
  733. {/* 分页 */}
  734. {filteredJobOrderPickOrders.length > 0 && (
  735. <TablePagination
  736. component="div"
  737. count={filteredJobOrderPickOrders.length}
  738. page={paginationController.pageNum}
  739. rowsPerPage={paginationController.pageSize}
  740. onPageChange={handlePageChange}
  741. onRowsPerPageChange={handlePageSizeChange}
  742. rowsPerPageOptions={[5, 10, 25, 50]}
  743. labelRowsPerPage={t("Rows per page")}
  744. />
  745. )}
  746. </Box>
  747. )}
  748. </Box>
  749. </FormProvider>
  750. );
  751. };
  752. export default CompleteJobOrderRecord;