"use client"; import React, { useCallback, useEffect, useState, useRef } from "react"; import { Box, Button, Paper, Stack, Typography, TextField, Table, TableBody, TableCell, TableContainer, TableHead, TableRow, Chip, Card, CardContent, CircularProgress, Dialog, DialogTitle, DialogContent, DialogActions, } from "@mui/material"; import QrCodeIcon from '@mui/icons-material/QrCode'; import { useTranslation } from "react-i18next"; import { Operator, Machine } from "@/app/api/jo"; import { useQrCodeScannerContext } from '../QrCodeScannerProvider/QrCodeScannerProvider'; import { useSession } from "next-auth/react"; import { SessionWithTokens } from "@/config/authConfig"; import PlayArrowIcon from "@mui/icons-material/PlayArrow"; import CheckCircleIcon from "@mui/icons-material/CheckCircle"; import dayjs from "dayjs"; import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; import { // updateProductProcessLineQrscan, newUpdateProductProcessLineQrscan, fetchProductProcessLineDetail, JobOrderProcessLineDetailResponse, ProductProcessLineInfoResponse, startProductProcessLine, fetchProductProcessesByJobOrderId } from "@/app/api/jo/actions"; import { fetchNameList, NameList } from "@/app/api/user/actions"; import ProductionProcessStepExecution from "./ProductionProcessStepExecution"; import ProductionOutputFormPage from "./ProductionOutputFormPage"; import ProcessSummaryHeader from "./ProcessSummaryHeader"; interface ProductProcessDetailProps { jobOrderId: number; onBack: () => void; fromJosave?: boolean; } const ProductionProcessDetail: React.FC = ({ jobOrderId, onBack, fromJosave, }) => { const { t } = useTranslation("common"); const { data: session } = useSession() as { data: SessionWithTokens | null }; const currentUserId = session?.id ? parseInt(session.id) : undefined; const { values: qrValues, startScan, stopScan, resetScan } = useQrCodeScannerContext(); const [showOutputPage, setShowOutputPage] = useState(false); // 基本信息 const [processData, setProcessData] = useState(null); const [lines, setLines] = useState([]); const [loading, setLoading] = useState(false); // 选中的 line 和执行状态 const [selectedLineId, setSelectedLineId] = useState(null); const [isExecutingLine, setIsExecutingLine] = useState(false); const [isAutoSubmitting, setIsAutoSubmitting] = useState(false); // 扫描器状态 const [isManualScanning, setIsManualScanning] = useState(false); const [processedQrCodes, setProcessedQrCodes] = useState>(new Set()); const [scannedOperatorId, setScannedOperatorId] = useState(null); const [scannedEquipmentId, setScannedEquipmentId] = useState(null); // const [scannedEquipmentTypeSubTypeEquipmentNo, setScannedEquipmentTypeSubTypeEquipmentNo] = useState(null); const [scannedStaffNo, setScannedStaffNo] = useState(null); // const [scannedEquipmentDetailId, setScannedEquipmentDetailId] = useState(null); const [scannedEquipmentCode, setScannedEquipmentCode] = useState(null); const [scanningLineId, setScanningLineId] = useState(null); const [lineDetailForScan, setLineDetailForScan] = useState(null); const [showScanDialog, setShowScanDialog] = useState(false); const autoSubmitTimerRef = useRef(null); // 产出表单 const [outputData, setOutputData] = useState({ byproductName: "", byproductQty: "", byproductUom: "", scrapQty: "", scrapUom: "", defectQty: "", defectUom: "", outputFromProcessQty: "", outputFromProcessUom: "", }); // 处理 QR 码扫描 // 处理 QR 码扫描 const handleBackFromStep = async () => { await fetchProcessDetail(); // 重新拉取最新的 process/lines setIsExecutingLine(false); setSelectedLineId(null); setShowOutputPage(false); }; // 获取 process 和 lines 数据 const fetchProcessDetail = useCallback(async () => { setLoading(true); try { console.log(`🔍 Loading process detail for JobOrderId: ${jobOrderId}`); // 使用 fetchProductProcessesByJobOrderId 获取基础数据 const processesWithLines = await fetchProductProcessesByJobOrderId(jobOrderId); if (!processesWithLines || processesWithLines.length === 0) { throw new Error("No processes found for this job order"); } // 如果有多个 process,取第一个(或者可以根据需要选择) const currentProcess = processesWithLines[0]; setProcessData(currentProcess); // 使用 productProcessLines 字段(API 返回的字段名) const lines = (currentProcess as any).productProcessLines || []; setLines(lines); console.log(" Process data loaded:", currentProcess); console.log(" Lines loaded:", lines); } catch (error) { console.error("❌ Error loading process detail:", error); //alert(`无法加载 Job Order ID ${jobOrderId} 的生产流程。该记录可能不存在。`); onBack(); } finally { setLoading(false); } }, [jobOrderId, onBack]); useEffect(() => { fetchProcessDetail(); }, [fetchProcessDetail]); // 开始执行某个 line // 提交产出数据 /* const processQrCode = useCallback((qrValue: string, lineId: number) => { // 操作员格式:{2fitestu1} - 键盘模拟输入(测试用) if (qrValue.match(/\{2fitestu(\d+)\}/)) { const match = qrValue.match(/\{2fitestu(\d+)\}/); const userId = parseInt(match![1]); fetchNameList().then((users: NameList[]) => { const user = users.find((u: NameList) => u.id === userId); if (user) { setScannedOperatorId(user.id); } }); return; } // 设备格式:{2fiteste1} - 键盘模拟输入(测试用) if (qrValue.match(/\{2fiteste(\d+)\}/)) { const match = qrValue.match(/\{2fiteste(\d+)\}/); const equipmentId = parseInt(match![1]); setScannedEquipmentId(equipmentId); return; } // 正常 QR 扫描器扫描:格式为 "operatorId: 1" 或 "equipmentId: 1" const trimmedValue = qrValue.trim(); // 检查 operatorId 格式 const operatorMatch = trimmedValue.match(/^operatorId:\s*(\d+)$/i); if (operatorMatch) { const operatorId = parseInt(operatorMatch[1]); fetchNameList().then((users: NameList[]) => { const user = users.find((u: NameList) => u.id === operatorId); if (user) { setScannedOperatorId(user.id); } else { console.warn(`User with ID ${operatorId} not found`); } }); return; } // 检查 equipmentId 格式 const equipmentMatch = trimmedValue.match(/^equipmentId:\s*(\d+)$/i); if (equipmentMatch) { const equipmentId = parseInt(equipmentMatch[1]); setScannedEquipmentId(equipmentId); return; } // 其他格式处理(JSON、普通文本等) try { const qrData = JSON.parse(qrValue); // TODO: 处理 JSON 格式的 QR 码 } catch { // 普通文本格式 // TODO: 处理普通文本格式 } }, []); */ // 提交产出数据 const processQrCode = useCallback((qrValue: string, lineId: number) => { // 设备快捷格式:{2fiteste数字} - 自动生成 equipmentTypeSubTypeEquipmentNo // 格式:{2fiteste数字} = line.equipment_name + "-数字號" // 例如:{2fiteste1} = "包裝機類-真空八爪魚機-1號" if (qrValue.match(/\{2fiteste(\d+)\}/)) { const match = qrValue.match(/\{2fiteste(\d+)\}/); const equipmentNo = parseInt(match![1]); // 根据 lineId 找到对应的 line const currentLine = lines.find(l => l.id === lineId); if (currentLine && currentLine.equipment_name) { const equipmentTypeSubTypeEquipmentNo = `${currentLine.equipment_name}-${equipmentNo}號`; setScannedEquipmentCode(equipmentTypeSubTypeEquipmentNo); console.log(`Generated equipmentTypeSubTypeEquipmentNo: ${equipmentTypeSubTypeEquipmentNo}`); } else { // 如果找不到 line,尝试从 API 获取 line detail console.warn(`Line with ID ${lineId} not found in current lines, fetching from API...`); fetchProductProcessLineDetail(lineId) .then((lineDetail) => { // 从 lineDetail 中获取 equipment_name const equipmentName = (lineDetail as any).equipment || (lineDetail as any).equipmentType || ""; if (equipmentName) { const equipmentTypeSubTypeEquipmentNo = `${equipmentName}-${equipmentNo}號`; setScannedEquipmentCode(equipmentTypeSubTypeEquipmentNo); console.log(`Generated equipmentTypeSubTypeEquipmentNo from API: ${equipmentTypeSubTypeEquipmentNo}`); } else { console.warn(`Equipment name not found in line detail for lineId: ${lineId}`); } }) .catch((err) => { console.error(`Failed to fetch line detail for lineId ${lineId}:`, err); }); } return; } // 员工编号格式:{2fitestu任何内容} - 直接作为 staffNo // 例如:{2fitestu123} = staffNo: "123" // 例如:{2fitestustaff001} = staffNo: "staff001" if (qrValue.match(/\{2fitestu(.+)\}/)) { const match = qrValue.match(/\{2fitestu(.+)\}/); const staffNo = match![1]; setScannedStaffNo(staffNo); return; } // 正常 QR 扫描器扫描格式 const trimmedValue = qrValue.trim(); // 检查 staffNo 格式:"staffNo: STAFF001" 或 "staffNo:STAFF001" const staffNoMatch = trimmedValue.match(/^staffNo:\s*(.+)$/i); if (staffNoMatch) { const staffNo = staffNoMatch[1].trim(); setScannedStaffNo(staffNo); return; } // 检查 equipmentCode 格式 const equipmentCodeMatch = trimmedValue.match(/^(?:equipmentTypeSubTypeEquipmentNo|EquipmentType-SubType-EquipmentNo|equipmentCode):\s*(.+)$/i); if (equipmentCodeMatch) { const equipmentCode = equipmentCodeMatch[1].trim(); setScannedEquipmentCode(equipmentCode); return; } // 其他格式处理(JSON、普通文本等) try { const qrData = JSON.parse(qrValue); if (qrData.staffNo) { setScannedStaffNo(String(qrData.staffNo)); } if (qrData.equipmentTypeSubTypeEquipmentNo || qrData.equipmentCode) { setScannedEquipmentCode( String(qrData.equipmentTypeSubTypeEquipmentNo ?? qrData.equipmentCode) ); } } catch { // 普通文本格式 - 尝试判断是 staffNo 还是 equipmentCode if (trimmedValue.length > 0) { if (trimmedValue.toUpperCase().startsWith("STAFF") || /^\d+$/.test(trimmedValue)) { // 可能是员工编号 setScannedStaffNo(trimmedValue); } else if (trimmedValue.includes("-")) { // 可能包含 "-" 的是设备代码(如 "包裝機類-真空八爪魚機-1號") setScannedEquipmentCode(trimmedValue); } } } }, [lines]); // 处理 QR 码扫描效果 useEffect(() => { if (isManualScanning && qrValues.length > 0 && scanningLineId) { const latestQr = qrValues[qrValues.length - 1]; if (processedQrCodes.has(latestQr)) { return; } setProcessedQrCodes(prev => new Set(prev).add(latestQr)); processQrCode(latestQr, scanningLineId); } }, [qrValues, isManualScanning, scanningLineId, processedQrCodes, processQrCode]); const submitScanAndStart = useCallback(async (lineId: number) => { console.log("submitScanAndStart called with:", { lineId, scannedStaffNo, // scannedEquipmentTypeSubTypeEquipmentNo, scannedEquipmentCode, }); if (!scannedStaffNo) { console.log("No staffNo, cannot submit"); setIsAutoSubmitting(false); return false; } try { const lineDetail = lineDetailForScan || await fetchProductProcessLineDetail(lineId); // ✅ 统一使用一个最终的 equipmentCode(优先用 scannedEquipmentCode,其次用 scannedEquipmentTypeSubTypeEquipmentNo) const effectiveEquipmentCode = scannedEquipmentCode ?? null; console.log("Submitting scan data with equipmentCode:", { productProcessLineId: lineId, staffNo: scannedStaffNo, equipmentCode: effectiveEquipmentCode, }); const response = await newUpdateProductProcessLineQrscan({ productProcessLineId: lineId, equipmentCode: effectiveEquipmentCode ?? "", staffNo: scannedStaffNo, }); console.log("Scan submit response:", response); if (response && response.type === "error") { console.error("Scan validation failed:", response.message); alert(t(response.message) || t("Validation failed. Please check your input.")); setIsAutoSubmitting(false); if (autoSubmitTimerRef.current) { clearTimeout(autoSubmitTimerRef.current); autoSubmitTimerRef.current = null; } return false; } console.log("Validation passed, starting line..."); handleStopScan(); setShowScanDialog(false); setIsAutoSubmitting(false); await handleStartLine(lineId); setSelectedLineId(lineId); setIsExecutingLine(true); await fetchProcessDetail(); return true; } catch (error) { console.error("Error submitting scan:", error); alert("Failed to submit scan data. Please try again."); setIsAutoSubmitting(false); return false; } }, [ scannedStaffNo, scannedEquipmentCode, lineDetailForScan, t, fetchProcessDetail, ]); const handleSubmitScanAndStart = useCallback(async (lineId: number) => { console.log("handleSubmitScanAndStart called with lineId:", lineId); if (!scannedStaffNo) { //alert(t("Please scan operator code first")); return; } // 如果正在自动提交,等待一下 if (isAutoSubmitting) { console.log("Already auto-submitting, skipping manual submit"); return; } await submitScanAndStart(lineId); }, [scannedOperatorId, isAutoSubmitting, submitScanAndStart, t]); // 开始扫描 const handleStartScan = useCallback((lineId: number) => { if (autoSubmitTimerRef.current) { clearTimeout(autoSubmitTimerRef.current); autoSubmitTimerRef.current = null; } setScanningLineId(lineId); setIsManualScanning(true); setProcessedQrCodes(new Set()); setScannedOperatorId(null); setScannedEquipmentId(null); setScannedStaffNo(null); // ✅ Add this setScannedEquipmentCode(null); setIsAutoSubmitting(false); // 添加:重置自动提交状态 setLineDetailForScan(null); // 获取 line detail 以获取 bomProcessEquipmentId fetchProductProcessLineDetail(lineId) .then(setLineDetailForScan) .catch(err => { console.error("Failed to load line detail", err); // 不阻止扫描继续,line detail 不是必需的 }); startScan(); }, [startScan]); // 停止扫描 const handleStopScan = useCallback(() => { console.log("🛑 Stopping scan"); // 清除定时器 if (autoSubmitTimerRef.current) { clearTimeout(autoSubmitTimerRef.current); autoSubmitTimerRef.current = null; } setIsManualScanning(false); setIsAutoSubmitting(false); setScannedStaffNo(null); // ✅ Add this setScannedEquipmentCode(null); stopScan(); resetScan(); }, [stopScan, resetScan]); // 开始执行某个 line(原有逻辑,现在在验证通过后调用) const handleStartLine = async (lineId: number) => { try { await startProductProcessLine(lineId); } catch (error) { console.error("Error starting line:", error); //alert("Failed to start line. Please try again."); } }; // 提交扫描结果并验证 /* useEffect(() => { console.log("Auto-submit check:", { scanningLineId, scannedStaffNo, scannedEquipmentCode, isAutoSubmitting, isManualScanning, }); // ✅ Update condition to check for either equipmentTypeSubTypeEquipmentNo OR equipmentDetailId if ( scanningLineId && scannedStaffNo !== null && (scannedEquipmentCode !== null) && !isAutoSubmitting && isManualScanning ) { console.log("Auto-submitting triggered!"); setIsAutoSubmitting(true); // 清除之前的定时器(如果有) if (autoSubmitTimerRef.current) { clearTimeout(autoSubmitTimerRef.current); } // 延迟一点时间,让用户看到两个都扫描完成了 autoSubmitTimerRef.current = setTimeout(() => { console.log("Executing auto-submit..."); submitScanAndStart(scanningLineId); autoSubmitTimerRef.current = null; }, 500); } // 清理函数:只在组件卸载或条件不再满足时清除定时器 return () => { // 注意:这里不立即清除定时器,因为我们需要它执行 // 只在组件卸载时清除 }; }, [scanningLineId, scannedStaffNo, scannedEquipmentCode, isAutoSubmitting, isManualScanning, submitScanAndStart]); */ useEffect(() => { return () => { if (autoSubmitTimerRef.current) { clearTimeout(autoSubmitTimerRef.current); } }; }, []); const handleStartLineWithScan = async (lineId: number) => { console.log("🚀 Starting line with scan for lineId:", lineId); // 确保状态完全重置 setIsAutoSubmitting(false); setScannedOperatorId(null); setScannedEquipmentId(null); setProcessedQrCodes(new Set()); setScannedStaffNo(null); setScannedEquipmentCode(null); setProcessedQrCodes(new Set()); // 清除之前的定时器 if (autoSubmitTimerRef.current) { clearTimeout(autoSubmitTimerRef.current); autoSubmitTimerRef.current = null; } setScanningLineId(lineId); setShowScanDialog(true); handleStartScan(lineId); }; const selectedLine = lines.find(l => l.id === selectedLineId); if (loading) { return ( ); } return ( {/* {t("Production Process Information")} {t("Job Order Code")}: {processData?.jobOrderCode} {t("Is Dark")}: {processData?.isDark} {t("Is Dense")}: {processData?.isDense} {t("Is Float")}: {processData?.isFloat} {t("Output Qty")}: {processData?.outputQty+" "+"("+processData?.outputQtyUom +")"} {t("Status")}:{" "} {t("Date")}: {dayjs(processData?.date).format(OUTPUT_DATE_FORMAT)} {t("Total Steps")}: {lines.length} */} {/* ========== 第二部分:Process Lines ========== */} {t("Production Process Steps")} {!isExecutingLine ? ( /* ========== 步骤列表视图 ========== */ {t("Seq")} {t("Step Name")} {t("Description")} {t("EquipmentType-EquipmentName-Code")} {t("Operator")} {/*} {t("Processing Time (mins)")} {t("Setup Time (mins)")} {t("Changeover Time (mins)")} */} {t("Time Information(mins)")} {/* {t("Processing Time")}- {t("Setup Time")} - {t("Changeover Time")} */} {t("Status")} {!fromJosave&&({t("Action")})} {lines.map((line) => { const status = (line as any).status || ''; const statusLower = status.toLowerCase(); const equipmentName = line.equipment_name || "-"; const isCompleted = statusLower === 'completed'; const isInProgress = statusLower === 'inprogress' || statusLower === 'in progress'; const isPaused = statusLower === 'paused'; const isPending = statusLower === 'pending' || status === ''; return ( {line.seqNo} {line.name} {line.description || "-"} {line.equipmentDetailCode||equipmentName} {line.operatorName} {/* {line.durationInMinutes} {line.prepTimeInMinutes} {line.postProdTimeInMinutes} */} {t("Processing Time")}: {line.durationInMinutes}{t("mins")} {t("Setup Time")}: {line.prepTimeInMinutes} {t("mins")} {t("Changeover Time")}: {line.postProdTimeInMinutes} {t("mins")} {isCompleted ? ( { setSelectedLineId(line.id); setShowOutputPage(false); // 不显示输出页面 setIsExecutingLine(true); await fetchProcessDetail(); }} /> ) : isInProgress ? ( { setSelectedLineId(line.id); setShowOutputPage(false); // 不显示输出页面 setIsExecutingLine(true); await fetchProcessDetail(); }} /> ) : isPending ? ( ) : isPaused ? ( ) : ( )} {!fromJosave&&( {statusLower === 'pending' ? ( ) : statusLower === 'in_progress' || statusLower === 'in progress' || statusLower === 'paused' ? ( ) : ( )} )} ); })}
) : ( /* ========== 步骤执行视图 ========== */ { // setIsExecutingLine(false) // setSelectedLineId(null) //}} //onOutputSubmitted={async () => { // await fetchProcessDetail() //}} /> )}
{/* QR 扫描对话框 */} { handleStopScan(); setShowScanDialog(false); }} maxWidth="sm" fullWidth > {t("Scan Operator & Equipment")} {scannedStaffNo ? `${t("Staff No")}: ${scannedStaffNo}` : t("Please scan staff no") } {/* ✅ Show both options */} {scannedEquipmentCode ? `${t("Equipment Code")}: ${scannedEquipmentCode}` : t("Please scan equipment code") }
); }; export default ProductionProcessDetail;