FPSMS-frontend
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

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