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.
 
 

820 regels
30 KiB

  1. "use client";
  2. import React, { useCallback, useEffect, useState, useRef } from "react";
  3. import {
  4. Box,
  5. Button,
  6. Paper,
  7. Stack,
  8. Typography,
  9. TextField,
  10. Table,
  11. TableBody,
  12. TableCell,
  13. TableContainer,
  14. TableHead,
  15. TableRow,
  16. Chip,
  17. Card,
  18. CardContent,
  19. CircularProgress,
  20. Dialog,
  21. DialogTitle,
  22. DialogContent,
  23. DialogActions,
  24. } from "@mui/material";
  25. import QrCodeIcon from '@mui/icons-material/QrCode';
  26. import { useTranslation } from "react-i18next";
  27. import { Operator, Machine } from "@/app/api/jo";
  28. import { useQrCodeScannerContext } from '../QrCodeScannerProvider/QrCodeScannerProvider';
  29. import { useSession } from "next-auth/react";
  30. import { SessionWithTokens } from "@/config/authConfig";
  31. import PlayArrowIcon from "@mui/icons-material/PlayArrow";
  32. import CheckCircleIcon from "@mui/icons-material/CheckCircle";
  33. import dayjs from "dayjs";
  34. import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil";
  35. import {
  36. // updateProductProcessLineQrscan,
  37. newUpdateProductProcessLineQrscan,
  38. fetchProductProcessLineDetail,
  39. JobOrderProcessLineDetailResponse,
  40. ProductProcessLineInfoResponse,
  41. startProductProcessLine,
  42. fetchProductProcessesByJobOrderId
  43. } from "@/app/api/jo/actions";
  44. import { fetchNameList, NameList } from "@/app/api/user/actions";
  45. import ProductionProcessStepExecution from "./ProductionProcessStepExecution";
  46. import ProductionOutputFormPage from "./ProductionOutputFormPage";
  47. import ProcessSummaryHeader from "./ProcessSummaryHeader";
  48. interface ProductProcessDetailProps {
  49. jobOrderId: number;
  50. onBack: () => void;
  51. fromJosave?: boolean;
  52. }
  53. const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({
  54. jobOrderId,
  55. onBack,
  56. fromJosave,
  57. }) => {
  58. const { t } = useTranslation("common");
  59. const { data: session } = useSession() as { data: SessionWithTokens | null };
  60. const currentUserId = session?.id ? parseInt(session.id) : undefined;
  61. const { values: qrValues, startScan, stopScan, resetScan } = useQrCodeScannerContext();
  62. const [showOutputPage, setShowOutputPage] = useState(false);
  63. // 基本信息
  64. const [processData, setProcessData] = useState<any>(null);
  65. const [lines, setLines] = useState<ProductProcessLineInfoResponse[]>([]);
  66. const [loading, setLoading] = useState(false);
  67. // 选中的 line 和执行状态
  68. const [selectedLineId, setSelectedLineId] = useState<number | null>(null);
  69. const [isExecutingLine, setIsExecutingLine] = useState(false);
  70. const [isAutoSubmitting, setIsAutoSubmitting] = useState(false);
  71. // 扫描器状态
  72. const [isManualScanning, setIsManualScanning] = useState(false);
  73. const [processedQrCodes, setProcessedQrCodes] = useState<Set<string>>(new Set());
  74. const [scannedOperatorId, setScannedOperatorId] = useState<number | null>(null);
  75. const [scannedEquipmentId, setScannedEquipmentId] = useState<number | null>(null);
  76. // const [scannedEquipmentTypeSubTypeEquipmentNo, setScannedEquipmentTypeSubTypeEquipmentNo] = useState<string | null>(null);
  77. const [scannedStaffNo, setScannedStaffNo] = useState<string | null>(null);
  78. // const [scannedEquipmentDetailId, setScannedEquipmentDetailId] = useState<number | null>(null);
  79. const [scannedEquipmentCode, setScannedEquipmentCode] = useState<string | null>(null);
  80. const [scanningLineId, setScanningLineId] = useState<number | null>(null);
  81. const [lineDetailForScan, setLineDetailForScan] = useState<JobOrderProcessLineDetailResponse | null>(null);
  82. const [showScanDialog, setShowScanDialog] = useState(false);
  83. const autoSubmitTimerRef = useRef<NodeJS.Timeout | null>(null);
  84. // 产出表单
  85. const [outputData, setOutputData] = useState({
  86. byproductName: "",
  87. byproductQty: "",
  88. byproductUom: "",
  89. scrapQty: "",
  90. scrapUom: "",
  91. defectQty: "",
  92. defectUom: "",
  93. outputFromProcessQty: "",
  94. outputFromProcessUom: "",
  95. });
  96. // 处理 QR 码扫描
  97. // 处理 QR 码扫描
  98. const handleBackFromStep = async () => {
  99. await fetchProcessDetail(); // 重新拉取最新的 process/lines
  100. setIsExecutingLine(false);
  101. setSelectedLineId(null);
  102. setShowOutputPage(false);
  103. };
  104. // 获取 process 和 lines 数据
  105. const fetchProcessDetail = useCallback(async () => {
  106. setLoading(true);
  107. try {
  108. console.log(`🔍 Loading process detail for JobOrderId: ${jobOrderId}`);
  109. // 使用 fetchProductProcessesByJobOrderId 获取基础数据
  110. const processesWithLines = await fetchProductProcessesByJobOrderId(jobOrderId);
  111. if (!processesWithLines || processesWithLines.length === 0) {
  112. throw new Error("No processes found for this job order");
  113. }
  114. // 如果有多个 process,取第一个(或者可以根据需要选择)
  115. const currentProcess = processesWithLines[0];
  116. setProcessData(currentProcess);
  117. // 使用 productProcessLines 字段(API 返回的字段名)
  118. const lines = (currentProcess as any).productProcessLines || [];
  119. setLines(lines);
  120. console.log(" Process data loaded:", currentProcess);
  121. console.log(" Lines loaded:", lines);
  122. } catch (error) {
  123. console.error("❌ Error loading process detail:", error);
  124. //alert(`无法加载 Job Order ID ${jobOrderId} 的生产流程。该记录可能不存在。`);
  125. onBack();
  126. } finally {
  127. setLoading(false);
  128. }
  129. }, [jobOrderId, onBack]);
  130. useEffect(() => {
  131. fetchProcessDetail();
  132. }, [fetchProcessDetail]);
  133. // 开始执行某个 line
  134. // 提交产出数据
  135. /*
  136. const processQrCode = useCallback((qrValue: string, lineId: number) => {
  137. // 操作员格式:{2fitestu1} - 键盘模拟输入(测试用)
  138. if (qrValue.match(/\{2fitestu(\d+)\}/)) {
  139. const match = qrValue.match(/\{2fitestu(\d+)\}/);
  140. const userId = parseInt(match![1]);
  141. fetchNameList().then((users: NameList[]) => {
  142. const user = users.find((u: NameList) => u.id === userId);
  143. if (user) {
  144. setScannedOperatorId(user.id);
  145. }
  146. });
  147. return;
  148. }
  149. // 设备格式:{2fiteste1} - 键盘模拟输入(测试用)
  150. if (qrValue.match(/\{2fiteste(\d+)\}/)) {
  151. const match = qrValue.match(/\{2fiteste(\d+)\}/);
  152. const equipmentId = parseInt(match![1]);
  153. setScannedEquipmentId(equipmentId);
  154. return;
  155. }
  156. // 正常 QR 扫描器扫描:格式为 "operatorId: 1" 或 "equipmentId: 1"
  157. const trimmedValue = qrValue.trim();
  158. // 检查 operatorId 格式
  159. const operatorMatch = trimmedValue.match(/^operatorId:\s*(\d+)$/i);
  160. if (operatorMatch) {
  161. const operatorId = parseInt(operatorMatch[1]);
  162. fetchNameList().then((users: NameList[]) => {
  163. const user = users.find((u: NameList) => u.id === operatorId);
  164. if (user) {
  165. setScannedOperatorId(user.id);
  166. } else {
  167. console.warn(`User with ID ${operatorId} not found`);
  168. }
  169. });
  170. return;
  171. }
  172. // 检查 equipmentId 格式
  173. const equipmentMatch = trimmedValue.match(/^equipmentId:\s*(\d+)$/i);
  174. if (equipmentMatch) {
  175. const equipmentId = parseInt(equipmentMatch[1]);
  176. setScannedEquipmentId(equipmentId);
  177. return;
  178. }
  179. // 其他格式处理(JSON、普通文本等)
  180. try {
  181. const qrData = JSON.parse(qrValue);
  182. // TODO: 处理 JSON 格式的 QR 码
  183. } catch {
  184. // 普通文本格式
  185. // TODO: 处理普通文本格式
  186. }
  187. }, []);
  188. */
  189. // 提交产出数据
  190. const processQrCode = useCallback((qrValue: string, lineId: number) => {
  191. // 设备快捷格式:{2fiteste数字} - 自动生成 equipmentTypeSubTypeEquipmentNo
  192. // 格式:{2fiteste数字} = line.equipment_name + "-数字號"
  193. // 例如:{2fiteste1} = "包裝機類-真空八爪魚機-1號"
  194. if (qrValue.match(/\{2fiteste(\d+)\}/)) {
  195. const match = qrValue.match(/\{2fiteste(\d+)\}/);
  196. const equipmentNo = parseInt(match![1]);
  197. // 根据 lineId 找到对应的 line
  198. const currentLine = lines.find(l => l.id === lineId);
  199. if (currentLine && currentLine.equipment_name) {
  200. const equipmentTypeSubTypeEquipmentNo = `${currentLine.equipment_name}-${equipmentNo}號`;
  201. setScannedEquipmentCode(equipmentTypeSubTypeEquipmentNo);
  202. console.log(`Generated equipmentTypeSubTypeEquipmentNo: ${equipmentTypeSubTypeEquipmentNo}`);
  203. } else {
  204. // 如果找不到 line,尝试从 API 获取 line detail
  205. console.warn(`Line with ID ${lineId} not found in current lines, fetching from API...`);
  206. fetchProductProcessLineDetail(lineId)
  207. .then((lineDetail) => {
  208. // 从 lineDetail 中获取 equipment_name
  209. const equipmentName = (lineDetail as any).equipment || (lineDetail as any).equipmentType || "";
  210. if (equipmentName) {
  211. const equipmentTypeSubTypeEquipmentNo = `${equipmentName}-${equipmentNo}號`;
  212. setScannedEquipmentCode(equipmentTypeSubTypeEquipmentNo);
  213. console.log(`Generated equipmentTypeSubTypeEquipmentNo from API: ${equipmentTypeSubTypeEquipmentNo}`);
  214. } else {
  215. console.warn(`Equipment name not found in line detail for lineId: ${lineId}`);
  216. }
  217. })
  218. .catch((err) => {
  219. console.error(`Failed to fetch line detail for lineId ${lineId}:`, err);
  220. });
  221. }
  222. return;
  223. }
  224. // 员工编号格式:{2fitestu任何内容} - 直接作为 staffNo
  225. // 例如:{2fitestu123} = staffNo: "123"
  226. // 例如:{2fitestustaff001} = staffNo: "staff001"
  227. if (qrValue.match(/\{2fitestu(.+)\}/)) {
  228. const match = qrValue.match(/\{2fitestu(.+)\}/);
  229. const staffNo = match![1];
  230. setScannedStaffNo(staffNo);
  231. return;
  232. }
  233. // 正常 QR 扫描器扫描格式
  234. const trimmedValue = qrValue.trim();
  235. // 检查 staffNo 格式:"staffNo: STAFF001" 或 "staffNo:STAFF001"
  236. const staffNoMatch = trimmedValue.match(/^staffNo:\s*(.+)$/i);
  237. if (staffNoMatch) {
  238. const staffNo = staffNoMatch[1].trim();
  239. setScannedStaffNo(staffNo);
  240. return;
  241. }
  242. // 检查 equipmentCode 格式
  243. const equipmentCodeMatch = trimmedValue.match(/^(?:equipmentTypeSubTypeEquipmentNo|EquipmentType-SubType-EquipmentNo|equipmentCode):\s*(.+)$/i);
  244. if (equipmentCodeMatch) {
  245. const equipmentCode = equipmentCodeMatch[1].trim();
  246. setScannedEquipmentCode(equipmentCode);
  247. return;
  248. }
  249. // 其他格式处理(JSON、普通文本等)
  250. try {
  251. const qrData = JSON.parse(qrValue);
  252. if (qrData.staffNo) {
  253. setScannedStaffNo(String(qrData.staffNo));
  254. }
  255. if (qrData.equipmentTypeSubTypeEquipmentNo || qrData.equipmentCode) {
  256. setScannedEquipmentCode(
  257. String(qrData.equipmentTypeSubTypeEquipmentNo ?? qrData.equipmentCode)
  258. );
  259. }
  260. } catch {
  261. // 普通文本格式 - 尝试判断是 staffNo 还是 equipmentCode
  262. if (trimmedValue.length > 0) {
  263. if (trimmedValue.toUpperCase().startsWith("STAFF") || /^\d+$/.test(trimmedValue)) {
  264. // 可能是员工编号
  265. setScannedStaffNo(trimmedValue);
  266. } else if (trimmedValue.includes("-")) {
  267. // 可能包含 "-" 的是设备代码(如 "包裝機類-真空八爪魚機-1號")
  268. setScannedEquipmentCode(trimmedValue);
  269. }
  270. }
  271. }
  272. }, [lines]);
  273. // 处理 QR 码扫描效果
  274. useEffect(() => {
  275. if (isManualScanning && qrValues.length > 0 && scanningLineId) {
  276. const latestQr = qrValues[qrValues.length - 1];
  277. if (processedQrCodes.has(latestQr)) {
  278. return;
  279. }
  280. setProcessedQrCodes(prev => new Set(prev).add(latestQr));
  281. processQrCode(latestQr, scanningLineId);
  282. }
  283. }, [qrValues, isManualScanning, scanningLineId, processedQrCodes, processQrCode]);
  284. const submitScanAndStart = useCallback(async (lineId: number) => {
  285. console.log("submitScanAndStart called with:", {
  286. lineId,
  287. scannedStaffNo,
  288. // scannedEquipmentTypeSubTypeEquipmentNo,
  289. scannedEquipmentCode,
  290. });
  291. if (!scannedStaffNo) {
  292. console.log("No staffNo, cannot submit");
  293. setIsAutoSubmitting(false);
  294. return false;
  295. }
  296. try {
  297. const lineDetail = lineDetailForScan || await fetchProductProcessLineDetail(lineId);
  298. // ✅ 统一使用一个最终的 equipmentCode(优先用 scannedEquipmentCode,其次用 scannedEquipmentTypeSubTypeEquipmentNo)
  299. const effectiveEquipmentCode =
  300. scannedEquipmentCode ?? null;
  301. console.log("Submitting scan data with equipmentCode:", {
  302. productProcessLineId: lineId,
  303. staffNo: scannedStaffNo,
  304. equipmentCode: effectiveEquipmentCode,
  305. });
  306. const response = await newUpdateProductProcessLineQrscan({
  307. productProcessLineId: lineId,
  308. equipmentCode: effectiveEquipmentCode ?? "",
  309. staffNo: scannedStaffNo,
  310. });
  311. console.log("Scan submit response:", response);
  312. if (response && response.type === "error") {
  313. console.error("Scan validation failed:", response.message);
  314. alert(t(response.message) || t("Validation failed. Please check your input."));
  315. setIsAutoSubmitting(false);
  316. if (autoSubmitTimerRef.current) {
  317. clearTimeout(autoSubmitTimerRef.current);
  318. autoSubmitTimerRef.current = null;
  319. }
  320. return false;
  321. }
  322. console.log("Validation passed, starting line...");
  323. handleStopScan();
  324. setShowScanDialog(false);
  325. setIsAutoSubmitting(false);
  326. await handleStartLine(lineId);
  327. setSelectedLineId(lineId);
  328. setIsExecutingLine(true);
  329. await fetchProcessDetail();
  330. return true;
  331. } catch (error) {
  332. console.error("Error submitting scan:", error);
  333. alert("Failed to submit scan data. Please try again.");
  334. setIsAutoSubmitting(false);
  335. return false;
  336. }
  337. }, [
  338. scannedStaffNo,
  339. scannedEquipmentCode,
  340. lineDetailForScan,
  341. t,
  342. fetchProcessDetail,
  343. ]);
  344. const handleSubmitScanAndStart = useCallback(async (lineId: number) => {
  345. console.log("handleSubmitScanAndStart called with lineId:", lineId);
  346. if (!scannedStaffNo) {
  347. //alert(t("Please scan operator code first"));
  348. return;
  349. }
  350. // 如果正在自动提交,等待一下
  351. if (isAutoSubmitting) {
  352. console.log("Already auto-submitting, skipping manual submit");
  353. return;
  354. }
  355. await submitScanAndStart(lineId);
  356. }, [scannedOperatorId, isAutoSubmitting, submitScanAndStart, t]);
  357. // 开始扫描
  358. const handleStartScan = useCallback((lineId: number) => {
  359. if (autoSubmitTimerRef.current) {
  360. clearTimeout(autoSubmitTimerRef.current);
  361. autoSubmitTimerRef.current = null;
  362. }
  363. setScanningLineId(lineId);
  364. setIsManualScanning(true);
  365. setProcessedQrCodes(new Set());
  366. setScannedOperatorId(null);
  367. setScannedEquipmentId(null);
  368. setScannedStaffNo(null); // ✅ Add this
  369. setScannedEquipmentCode(null);
  370. setIsAutoSubmitting(false); // 添加:重置自动提交状态
  371. setLineDetailForScan(null);
  372. // 获取 line detail 以获取 bomProcessEquipmentId
  373. fetchProductProcessLineDetail(lineId)
  374. .then(setLineDetailForScan)
  375. .catch(err => {
  376. console.error("Failed to load line detail", err);
  377. // 不阻止扫描继续,line detail 不是必需的
  378. });
  379. startScan();
  380. }, [startScan]);
  381. // 停止扫描
  382. const handleStopScan = useCallback(() => {
  383. console.log("🛑 Stopping scan");
  384. // 清除定时器
  385. if (autoSubmitTimerRef.current) {
  386. clearTimeout(autoSubmitTimerRef.current);
  387. autoSubmitTimerRef.current = null;
  388. }
  389. setIsManualScanning(false);
  390. setIsAutoSubmitting(false);
  391. setScannedStaffNo(null); // ✅ Add this
  392. setScannedEquipmentCode(null);
  393. stopScan();
  394. resetScan();
  395. }, [stopScan, resetScan]);
  396. // 开始执行某个 line(原有逻辑,现在在验证通过后调用)
  397. const handleStartLine = async (lineId: number) => {
  398. try {
  399. await startProductProcessLine(lineId);
  400. } catch (error) {
  401. console.error("Error starting line:", error);
  402. //alert("Failed to start line. Please try again.");
  403. }
  404. };
  405. // 提交扫描结果并验证
  406. /*
  407. useEffect(() => {
  408. console.log("Auto-submit check:", {
  409. scanningLineId,
  410. scannedStaffNo,
  411. scannedEquipmentCode,
  412. isAutoSubmitting,
  413. isManualScanning,
  414. });
  415. // ✅ Update condition to check for either equipmentTypeSubTypeEquipmentNo OR equipmentDetailId
  416. if (
  417. scanningLineId &&
  418. scannedStaffNo !== null &&
  419. (scannedEquipmentCode !== null) &&
  420. !isAutoSubmitting &&
  421. isManualScanning
  422. ) {
  423. console.log("Auto-submitting triggered!");
  424. setIsAutoSubmitting(true);
  425. // 清除之前的定时器(如果有)
  426. if (autoSubmitTimerRef.current) {
  427. clearTimeout(autoSubmitTimerRef.current);
  428. }
  429. // 延迟一点时间,让用户看到两个都扫描完成了
  430. autoSubmitTimerRef.current = setTimeout(() => {
  431. console.log("Executing auto-submit...");
  432. submitScanAndStart(scanningLineId);
  433. autoSubmitTimerRef.current = null;
  434. }, 500);
  435. }
  436. // 清理函数:只在组件卸载或条件不再满足时清除定时器
  437. return () => {
  438. // 注意:这里不立即清除定时器,因为我们需要它执行
  439. // 只在组件卸载时清除
  440. };
  441. }, [scanningLineId, scannedStaffNo, scannedEquipmentCode, isAutoSubmitting, isManualScanning, submitScanAndStart]);
  442. */
  443. useEffect(() => {
  444. return () => {
  445. if (autoSubmitTimerRef.current) {
  446. clearTimeout(autoSubmitTimerRef.current);
  447. }
  448. };
  449. }, []);
  450. const handleStartLineWithScan = async (lineId: number) => {
  451. console.log("🚀 Starting line with scan for lineId:", lineId);
  452. // 确保状态完全重置
  453. setIsAutoSubmitting(false);
  454. setScannedOperatorId(null);
  455. setScannedEquipmentId(null);
  456. setProcessedQrCodes(new Set());
  457. setScannedStaffNo(null);
  458. setScannedEquipmentCode(null);
  459. setProcessedQrCodes(new Set());
  460. // 清除之前的定时器
  461. if (autoSubmitTimerRef.current) {
  462. clearTimeout(autoSubmitTimerRef.current);
  463. autoSubmitTimerRef.current = null;
  464. }
  465. setScanningLineId(lineId);
  466. setShowScanDialog(true);
  467. handleStartScan(lineId);
  468. };
  469. const selectedLine = lines.find(l => l.id === selectedLineId);
  470. if (loading) {
  471. return (
  472. <Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
  473. <CircularProgress/>
  474. </Box>
  475. );
  476. }
  477. return (
  478. <Box>
  479. {/*
  480. <Box sx={{ mb: 2 }}>
  481. <Button variant="outlined" onClick={onBack}>
  482. {t("Back to List")}
  483. </Button>
  484. </Box>
  485. <Paper sx={{ p: 3, mb: 3 }}>
  486. <Typography variant="h6" gutterBottom fontWeight="bold">
  487. {t("Production Process Information")}
  488. </Typography>
  489. <Stack spacing={2} direction="row" useFlexGap flexWrap="wrap">
  490. <Typography variant="subtitle1">
  491. <strong>{t("Job Order Code")}:</strong> {processData?.jobOrderCode}
  492. </Typography>
  493. <Typography variant="subtitle1">
  494. <strong>{t("Is Dark")}:</strong> {processData?.isDark}
  495. </Typography>
  496. <Typography variant="subtitle1">
  497. <strong>{t("Is Dense")}:</strong> {processData?.isDense}
  498. </Typography>
  499. <Typography variant="subtitle1">
  500. <strong>{t("Is Float")}:</strong> {processData?.isFloat}
  501. </Typography>
  502. <Typography variant="subtitle1">
  503. <strong>{t("Output Qty")}:</strong> {processData?.outputQty+" "+"("+processData?.outputQtyUom +")"}
  504. </Typography>
  505. <Box>
  506. <strong>{t("Status")}:</strong>{" "}
  507. <Chip
  508. label={
  509. processData?.status === 'completed' ? t("Completed") : processData?.status === 'IN_PROGRESS' ? t("In Progress") : processData?.status === 'pending' ? t("Pending") : t("Unknown")
  510. }
  511. color={processData?.status === 'completed' ? 'success' : processData?.status === 'IN_PROGRESS' ? 'success' : processData?.status === 'pending' ? 'primary' : 'error'}
  512. size="small"
  513. />
  514. </Box>
  515. <Typography variant="subtitle1">
  516. <strong>{t("Date")}:</strong> {dayjs(processData?.date).format(OUTPUT_DATE_FORMAT)}
  517. </Typography>
  518. <Typography variant="subtitle1">
  519. <strong>{t("Total Steps")}:</strong> {lines.length}
  520. </Typography>
  521. </Stack>
  522. </Paper>
  523. */}
  524. {/* ========== 第二部分:Process Lines ========== */}
  525. <Paper sx={{ p: 3 }}>
  526. <Typography variant="h6" gutterBottom fontWeight="bold">
  527. {t("Production Process Steps")}
  528. </Typography>
  529. <ProcessSummaryHeader processData={processData} />
  530. {!isExecutingLine ? (
  531. /* ========== 步骤列表视图 ========== */
  532. <TableContainer>
  533. <Table>
  534. <TableHead>
  535. <TableRow>
  536. <TableCell>{t("Seq")}</TableCell>
  537. <TableCell>{t("Step Name")}</TableCell>
  538. <TableCell>{t("Description")}</TableCell>
  539. <TableCell>{t("EquipmentType-EquipmentName-Code")}</TableCell>
  540. <TableCell>{t("Operator")}</TableCell>
  541. {/*}
  542. <TableCell>{t("Processing Time (mins)")}</TableCell>
  543. <TableCell>{t("Setup Time (mins)")}</TableCell>
  544. <TableCell>{t("Changeover Time (mins)")}</TableCell>
  545. */}
  546. <TableCell>
  547. <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
  548. <Typography variant="subtitle2" sx={{ fontWeight: 600 }}>
  549. {t("Time Information(mins)")}
  550. </Typography>
  551. {/*
  552. <Typography variant="caption" sx={{ color: 'text.secondary' }}>
  553. {t("Processing Time")}-
  554. </Typography>
  555. <Typography variant="caption" sx={{ color: 'text.secondary' }}>
  556. {t("Setup Time")} - {t("Changeover Time")}
  557. </Typography>
  558. */}
  559. </Box>
  560. </TableCell>
  561. <TableCell align="center">{t("Status")}</TableCell>
  562. {!fromJosave&&(<TableCell align="center">{t("Action")}</TableCell>)}
  563. </TableRow>
  564. </TableHead>
  565. <TableBody>
  566. {lines.map((line) => {
  567. const status = (line as any).status || '';
  568. const statusLower = status.toLowerCase();
  569. const equipmentName = line.equipment_name || "-";
  570. const isCompleted = statusLower === 'completed';
  571. const isInProgress = statusLower === 'inprogress' || statusLower === 'in progress';
  572. const isPaused = statusLower === 'paused';
  573. const isPending = statusLower === 'pending' || status === '';
  574. return (
  575. <TableRow key={line.id}>
  576. <TableCell>{line.seqNo}</TableCell>
  577. <TableCell>
  578. <Typography fontWeight={500}>{line.name}</Typography>
  579. </TableCell>
  580. <TableCell><Typography fontWeight={500}>{line.description || "-"}</Typography></TableCell>
  581. <TableCell><Typography fontWeight={500}>{line.equipmentDetailCode||equipmentName}</Typography></TableCell>
  582. <TableCell><Typography fontWeight={500}>{line.operatorName}</Typography></TableCell>
  583. {/*
  584. <TableCell><Typography fontWeight={500}>{line.durationInMinutes} </Typography></TableCell>
  585. <TableCell><Typography fontWeight={500}>{line.prepTimeInMinutes} </Typography></TableCell>
  586. <TableCell><Typography fontWeight={500}>{line.postProdTimeInMinutes} </Typography></TableCell>
  587. */}
  588. <TableCell>
  589. <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
  590. <Typography variant="body2" >
  591. {t("Processing Time")}: {line.durationInMinutes}{t("mins")}
  592. </Typography>
  593. <Typography variant="body2" >
  594. {t("Setup Time")}: {line.prepTimeInMinutes} {t("mins")}
  595. </Typography>
  596. <Typography variant="body2" >
  597. {t("Changeover Time")}: {line.postProdTimeInMinutes} {t("mins")}
  598. </Typography>
  599. </Box>
  600. </TableCell>
  601. <TableCell align="center">
  602. {isCompleted ? (
  603. <Chip label={t("Completed")} color="success" size="small"
  604. onClick={async () => {
  605. setSelectedLineId(line.id);
  606. setShowOutputPage(false); // 不显示输出页面
  607. setIsExecutingLine(true);
  608. await fetchProcessDetail();
  609. }}
  610. />
  611. ) : isInProgress ? (
  612. <Chip label={t("In Progress")} color="primary" size="small"
  613. onClick={async () => {
  614. setSelectedLineId(line.id);
  615. setShowOutputPage(false); // 不显示输出页面
  616. setIsExecutingLine(true);
  617. await fetchProcessDetail();
  618. }} />
  619. ) : isPending ? (
  620. <Chip label={t("Pending")} color="default" size="small" />
  621. ) : isPaused ? (
  622. <Chip label={t("Paused")} color="warning" size="small" />
  623. ) : (
  624. <Chip label={t("Unknown")} color="error" size="small" />
  625. )}
  626. </TableCell>
  627. {!fromJosave&&(
  628. <TableCell align="center">
  629. {statusLower === 'pending' ? (
  630. <Button
  631. variant="contained"
  632. size="small"
  633. startIcon={<PlayArrowIcon />}
  634. onClick={() => handleStartLineWithScan(line.id)}
  635. >
  636. {t("Start")}
  637. </Button>
  638. ) : statusLower === 'in_progress' || statusLower === 'in progress' || statusLower === 'paused' ? (
  639. <Button
  640. variant="contained"
  641. size="small"
  642. startIcon={<CheckCircleIcon />}
  643. onClick={async () => {
  644. setSelectedLineId(line.id);
  645. setIsExecutingLine(true);
  646. await fetchProcessDetail();
  647. }}
  648. >
  649. {t("View")}
  650. </Button>
  651. ) : (
  652. <Button
  653. variant="outlined"
  654. size="small"
  655. onClick={async() => {
  656. setSelectedLineId(line.id);
  657. setIsExecutingLine(true);
  658. await fetchProcessDetail();
  659. }}
  660. >
  661. {t("View")}
  662. </Button>
  663. )}
  664. </TableCell>
  665. )}
  666. </TableRow>
  667. );
  668. })}
  669. </TableBody>
  670. </Table>
  671. </TableContainer>
  672. ) : (
  673. /* ========== 步骤执行视图 ========== */
  674. <ProductionProcessStepExecution
  675. lineId={selectedLineId}
  676. onBack={handleBackFromStep}
  677. //onClose={() => {
  678. // setIsExecutingLine(false)
  679. // setSelectedLineId(null)
  680. //}}
  681. //onOutputSubmitted={async () => {
  682. // await fetchProcessDetail()
  683. //}}
  684. />
  685. )}
  686. </Paper>
  687. {/* QR 扫描对话框 */}
  688. <Dialog
  689. open={showScanDialog}
  690. onClose={() => {
  691. handleStopScan();
  692. setShowScanDialog(false);
  693. }}
  694. maxWidth="sm"
  695. fullWidth
  696. >
  697. <DialogTitle>{t("Scan Operator & Equipment")}</DialogTitle>
  698. <DialogContent>
  699. <Stack spacing={2} sx={{ mt: 2 }}>
  700. <Box>
  701. <Typography variant="body2" color="text.secondary">
  702. {scannedStaffNo
  703. ? `${t("Staff No")}: ${scannedStaffNo}`
  704. : t("Please scan staff no")
  705. }
  706. </Typography>
  707. </Box>
  708. <Box>
  709. <Typography variant="body2" color="text.secondary">
  710. {/* ✅ Show both options */}
  711. {scannedEquipmentCode
  712. ? `${t("Equipment Code")}: ${scannedEquipmentCode}`
  713. : t("Please scan equipment code")
  714. }
  715. </Typography>
  716. </Box>
  717. <Button
  718. variant={isManualScanning ? "outlined" : "contained"}
  719. startIcon={<QrCodeIcon />}
  720. onClick={isManualScanning ? handleStopScan : () => scanningLineId && handleStartScan(scanningLineId)}
  721. color={isManualScanning ? "secondary" : "primary"}
  722. fullWidth
  723. >
  724. {isManualScanning ? t("Stop QR Scan") : t("Start QR Scan")}
  725. </Button>
  726. </Stack>
  727. </DialogContent>
  728. <DialogActions>
  729. <Button onClick={() => {
  730. handleStopScan();
  731. setShowScanDialog(false);
  732. }}>
  733. {t("Cancel")}
  734. </Button>
  735. <Button
  736. variant="contained"
  737. onClick={() => scanningLineId && handleSubmitScanAndStart(scanningLineId)}
  738. disabled={!scannedStaffNo }
  739. >
  740. {t("Submit & Start")}
  741. </Button>
  742. </DialogActions>
  743. </Dialog>
  744. </Box>
  745. );
  746. };
  747. export default ProductionProcessDetail;