|
|
|
@@ -17,6 +17,10 @@ import { |
|
|
|
Card, |
|
|
|
CardContent, |
|
|
|
CircularProgress, |
|
|
|
Dialog, |
|
|
|
DialogTitle, |
|
|
|
DialogContent, |
|
|
|
DialogActions, |
|
|
|
} from "@mui/material"; |
|
|
|
import QrCodeIcon from '@mui/icons-material/QrCode'; |
|
|
|
import { useTranslation } from "react-i18next"; |
|
|
|
@@ -75,13 +79,17 @@ const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({ |
|
|
|
// 选中的 line 和执行状态 |
|
|
|
const [selectedLineId, setSelectedLineId] = useState<number | null>(null); |
|
|
|
const [isExecutingLine, setIsExecutingLine] = useState(false); |
|
|
|
|
|
|
|
const [isAutoSubmitting, setIsAutoSubmitting] = useState(false); |
|
|
|
// 扫描器状态 |
|
|
|
const [isManualScanning, setIsManualScanning] = useState(false); |
|
|
|
const [processedQrCodes, setProcessedQrCodes] = useState<Set<string>>(new Set()); |
|
|
|
const [scannedOperators, setScannedOperators] = useState<Operator[]>([]); |
|
|
|
const [scannedMachines, setScannedMachines] = useState<Machine[]>([]); |
|
|
|
|
|
|
|
const [scannedOperatorId, setScannedOperatorId] = useState<number | null>(null); |
|
|
|
const [scannedEquipmentId, setScannedEquipmentId] = useState<number | null>(null); |
|
|
|
const [scanningLineId, setScanningLineId] = useState<number | null>(null); |
|
|
|
const [lineDetailForScan, setLineDetailForScan] = useState<ProductProcessLineDetailResponse | null>(null); |
|
|
|
const [showScanDialog, setShowScanDialog] = useState(false); |
|
|
|
const autoSubmitTimerRef = useRef<NodeJS.Timeout | null>(null); |
|
|
|
|
|
|
|
// 产出表单 |
|
|
|
const [outputData, setOutputData] = useState({ |
|
|
|
byproductName: "", |
|
|
|
@@ -97,133 +105,7 @@ const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({ |
|
|
|
|
|
|
|
// 处理 QR 码扫描 |
|
|
|
// 处理 QR 码扫描 |
|
|
|
const processQrCode = useCallback((qrValue: string) => { |
|
|
|
// 操作员格式:{2fitestu1} - 键盘模拟输入(测试用) |
|
|
|
if (qrValue.match(/\{2fitestu(\d+)\}/)) { |
|
|
|
const match = qrValue.match(/\{2fitestu(\d+)\}/); |
|
|
|
const userId = parseInt(match![1]); |
|
|
|
|
|
|
|
// 调用 API 获取用户信息 |
|
|
|
fetchNameList().then((users: NameList[]) => { |
|
|
|
const user = users.find((u: NameList) => u.id === userId); |
|
|
|
if (user) { |
|
|
|
setScannedOperators([{ |
|
|
|
id: user.id, |
|
|
|
name: user.name, |
|
|
|
username: user.name |
|
|
|
}]); |
|
|
|
|
|
|
|
updateProductProcessLineQrscan({ |
|
|
|
lineId: selectedLineId || 0 as number, |
|
|
|
operatorId: user.id, |
|
|
|
}); |
|
|
|
} |
|
|
|
}); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// 设备格式:{2fiteste1} - 键盘模拟输入(测试用) |
|
|
|
if (qrValue.match(/\{2fiteste(\d+)\}/)) { |
|
|
|
const match = qrValue.match(/\{2fiteste(\d+)\}/); |
|
|
|
const equipmentId = parseInt(match![1]); |
|
|
|
|
|
|
|
// 使用本地设备数据库 |
|
|
|
const machine = machineDatabase.find((m: Machine) => m.id === equipmentId); |
|
|
|
if (machine) { |
|
|
|
setScannedMachines([machine]); |
|
|
|
} |
|
|
|
|
|
|
|
updateProductProcessLineQrscan({ |
|
|
|
lineId: selectedLineId || 0 as number, |
|
|
|
equipmentId: equipmentId, |
|
|
|
}).then((res) => { |
|
|
|
console.log(res); |
|
|
|
}); |
|
|
|
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) { |
|
|
|
setScannedOperators([{ |
|
|
|
id: user.id, |
|
|
|
name: user.name, |
|
|
|
username: user.name |
|
|
|
}]); |
|
|
|
|
|
|
|
updateProductProcessLineQrscan({ |
|
|
|
lineId: selectedLineId || 0 as number, |
|
|
|
operatorId: 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]); |
|
|
|
const machine = machineDatabase.find((m: Machine) => m.id === equipmentId); |
|
|
|
if (machine) { |
|
|
|
setScannedMachines([machine]); |
|
|
|
} |
|
|
|
|
|
|
|
updateProductProcessLineQrscan({ |
|
|
|
lineId: selectedLineId || 0 as number, |
|
|
|
equipmentId: equipmentId, |
|
|
|
}).then((res) => { |
|
|
|
console.log(res); |
|
|
|
}); |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
// 其他格式处理(JSON、普通文本等) |
|
|
|
try { |
|
|
|
const qrData = JSON.parse(qrValue); |
|
|
|
// TODO: 处理 JSON 格式的 QR 码 |
|
|
|
} catch { |
|
|
|
// 普通文本格式 |
|
|
|
// TODO: 处理普通文本格式 |
|
|
|
} |
|
|
|
}, [selectedLineId]); |
|
|
|
|
|
|
|
// 处理 QR 码扫描效果 |
|
|
|
useEffect(() => { |
|
|
|
if (isManualScanning && qrValues.length > 0 && isExecutingLine) { |
|
|
|
const latestQr = qrValues[qrValues.length - 1]; |
|
|
|
|
|
|
|
if (processedQrCodes.has(latestQr)) { |
|
|
|
return; |
|
|
|
} |
|
|
|
|
|
|
|
setProcessedQrCodes(prev => new Set(prev).add(latestQr)); |
|
|
|
processQrCode(latestQr); |
|
|
|
} |
|
|
|
}, [qrValues, isManualScanning, isExecutingLine, processedQrCodes, processQrCode]); |
|
|
|
|
|
|
|
// 开始扫描 |
|
|
|
const handleStartScan = useCallback(() => { |
|
|
|
setIsManualScanning(true); |
|
|
|
setProcessedQrCodes(new Set()); |
|
|
|
startScan(); |
|
|
|
}, [startScan]); |
|
|
|
|
|
|
|
// 停止扫描 |
|
|
|
const handleStopScan = useCallback(() => { |
|
|
|
setIsManualScanning(false); |
|
|
|
stopScan(); |
|
|
|
resetScan(); |
|
|
|
}, [stopScan, resetScan]); |
|
|
|
|
|
|
|
|
|
|
|
// 获取 process 和 lines 数据 |
|
|
|
const fetchProcessDetail = useCallback(async () => { |
|
|
|
@@ -263,53 +145,239 @@ const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({ |
|
|
|
}, [fetchProcessDetail]); |
|
|
|
|
|
|
|
// 开始执行某个 line |
|
|
|
const handleStartLine = async (lineId: number) => { |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
// 使用 Server Action 而不是直接 fetch |
|
|
|
await startProductProcessLine(lineId); |
|
|
|
// 提交产出数据 |
|
|
|
const processQrCode = useCallback((qrValue: string, lineId: number) => { |
|
|
|
// 操作员格式:{2fitestu1} - 键盘模拟输入(测试用) |
|
|
|
if (qrValue.match(/\{2fitestu(\d+)\}/)) { |
|
|
|
const match = qrValue.match(/\{2fitestu(\d+)\}/); |
|
|
|
const userId = parseInt(match![1]); |
|
|
|
|
|
|
|
// 刷新数据 |
|
|
|
//await fetchProcessDetail(); |
|
|
|
} catch (error) { |
|
|
|
console.error("Error starting line:", error); |
|
|
|
alert("Failed to start line. Please try again."); |
|
|
|
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; |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// 提交产出数据 |
|
|
|
const handleSubmitOutput = async () => { |
|
|
|
if (!selectedLineId) return; |
|
|
|
|
|
|
|
if (scannedOperators.length === 0 || scannedMachines.length === 0) { |
|
|
|
alert("Please scan operator and machine first!"); |
|
|
|
|
|
|
|
// 正常 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: 处理普通文本格式 |
|
|
|
} |
|
|
|
}, []); |
|
|
|
|
|
|
|
// 处理 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, |
|
|
|
scannedOperatorId, |
|
|
|
scannedEquipmentId, |
|
|
|
}); |
|
|
|
|
|
|
|
if (!scannedOperatorId) { |
|
|
|
console.log("No operatorId, cannot submit"); |
|
|
|
return false; // 没有 operatorId,不能提交 |
|
|
|
} |
|
|
|
|
|
|
|
try { |
|
|
|
// 直接使用 actions.ts 中定义的函数 |
|
|
|
await updateLineOutput(selectedLineId, { |
|
|
|
outputQty: parseFloat(outputData.outputFromProcessQty) || 0, |
|
|
|
outputUom: outputData.outputFromProcessUom, |
|
|
|
defectQty: parseFloat(outputData.defectQty) || 0, |
|
|
|
defectUom: outputData.defectUom, |
|
|
|
scrapQty: parseFloat(outputData.scrapQty) || 0, |
|
|
|
scrapUom: outputData.scrapUom, |
|
|
|
byproductName: outputData.byproductName, |
|
|
|
byproductQty: parseFloat(outputData.byproductQty) || 0, |
|
|
|
byproductUom: outputData.byproductUom, |
|
|
|
// 获取 line detail 以检查 bomProcessEquipmentId |
|
|
|
const lineDetail = lineDetailForScan || await fetchProductProcessLineDetail(lineId); |
|
|
|
|
|
|
|
// 提交 operatorId 和 equipmentId |
|
|
|
console.log("Submitting scan data:", { |
|
|
|
productProcessLineId: lineId, |
|
|
|
operatorId: scannedOperatorId, |
|
|
|
equipmentId: scannedEquipmentId || undefined, |
|
|
|
}); |
|
|
|
|
|
|
|
const response = await updateProductProcessLineQrscan({ |
|
|
|
productProcessLineId: lineId, |
|
|
|
operatorId: scannedOperatorId, |
|
|
|
equipmentId: scannedEquipmentId || undefined, |
|
|
|
}); |
|
|
|
|
|
|
|
console.log(" Output data submitted successfully"); |
|
|
|
setIsExecutingLine(false); |
|
|
|
setSelectedLineId(null); |
|
|
|
handleStopScan(); |
|
|
|
await fetchProcessDetail(); |
|
|
|
console.log("Scan submit response:", response); |
|
|
|
|
|
|
|
// 检查响应中的 message 字段来判断是否成功 |
|
|
|
// 如果后端返回 message 不为 null,说明验证失败 |
|
|
|
if (response && response.message) { |
|
|
|
//alert(response.message || t("Validation failed. Please check operator and equipment.")); |
|
|
|
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(t("Failed to submit scan data. Please try again.")); |
|
|
|
setIsAutoSubmitting(false); |
|
|
|
return false; |
|
|
|
} |
|
|
|
}, [scannedOperatorId, scannedEquipmentId, lineDetailForScan, t, fetchProcessDetail]); |
|
|
|
const handleSubmitScanAndStart = useCallback(async (lineId: number) => { |
|
|
|
console.log("handleSubmitScanAndStart called with lineId:", lineId); |
|
|
|
|
|
|
|
if (!scannedOperatorId) { |
|
|
|
//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) => { |
|
|
|
setScanningLineId(lineId); |
|
|
|
setIsManualScanning(true); |
|
|
|
setProcessedQrCodes(new Set()); |
|
|
|
setScannedOperatorId(null); |
|
|
|
setScannedEquipmentId(null); |
|
|
|
// 获取 line detail 以获取 bomProcessEquipmentId |
|
|
|
fetchProductProcessLineDetail(lineId) |
|
|
|
.then(setLineDetailForScan) |
|
|
|
.catch(err => console.error("Failed to load line detail", err)); |
|
|
|
startScan(); |
|
|
|
}, [startScan]); |
|
|
|
|
|
|
|
// 停止扫描 |
|
|
|
const handleStopScan = useCallback(() => { |
|
|
|
setIsManualScanning(false); |
|
|
|
stopScan(); |
|
|
|
resetScan(); |
|
|
|
}, [stopScan, resetScan]); |
|
|
|
|
|
|
|
// 开始执行某个 line(原有逻辑,现在在验证通过后调用) |
|
|
|
const handleStartLine = async (lineId: number) => { |
|
|
|
try { |
|
|
|
await startProductProcessLine(lineId); |
|
|
|
} catch (error) { |
|
|
|
console.error("Error submitting output:", error); |
|
|
|
alert("Failed to submit output data. Please try again."); |
|
|
|
console.error("Error starting line:", error); |
|
|
|
//alert("Failed to start line. Please try again."); |
|
|
|
} |
|
|
|
}; |
|
|
|
// 提交扫描结果并验证 |
|
|
|
|
|
|
|
useEffect(() => { |
|
|
|
console.log("Auto-submit check:", { |
|
|
|
scanningLineId, |
|
|
|
scannedOperatorId, |
|
|
|
scannedEquipmentId, |
|
|
|
isAutoSubmitting, |
|
|
|
isManualScanning, |
|
|
|
}); |
|
|
|
|
|
|
|
if ( |
|
|
|
scanningLineId && |
|
|
|
scannedOperatorId !== null && |
|
|
|
scannedEquipmentId !== 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, scannedOperatorId, scannedEquipmentId, isAutoSubmitting, isManualScanning, submitScanAndStart]); |
|
|
|
useEffect(() => { |
|
|
|
return () => { |
|
|
|
if (autoSubmitTimerRef.current) { |
|
|
|
clearTimeout(autoSubmitTimerRef.current); |
|
|
|
} |
|
|
|
}; |
|
|
|
}, []); |
|
|
|
|
|
|
|
const handleStartLineWithScan = async (lineId: number) => { |
|
|
|
setScanningLineId(lineId); |
|
|
|
setShowScanDialog(true); |
|
|
|
handleStartScan(lineId); |
|
|
|
}; |
|
|
|
|
|
|
|
const selectedLine = lines.find(l => l.id === selectedLineId); |
|
|
|
|
|
|
|
@@ -337,16 +405,30 @@ const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({ |
|
|
|
</Typography> |
|
|
|
<Stack spacing={2} direction="row" useFlexGap flexWrap="wrap"> |
|
|
|
<Typography variant="subtitle1"> |
|
|
|
<strong>{t("Process Code")}:</strong> {processData?.productProcessCode} |
|
|
|
<strong>{t("Job Order Code")}:</strong> {processData?.jobOrderCode} |
|
|
|
</Typography> |
|
|
|
<Typography variant="subtitle1"> |
|
|
|
<strong>{t("Is Dark")}:</strong> {processData?.isDark} |
|
|
|
</Typography> |
|
|
|
<Typography variant="subtitle1"> |
|
|
|
<strong>{t("Is Dense")}:</strong> {processData?.isDense} |
|
|
|
</Typography> |
|
|
|
<Typography variant="subtitle1"> |
|
|
|
<strong>{t("Is Float")}:</strong> {processData?.isFloat} |
|
|
|
</Typography> |
|
|
|
<Typography variant="subtitle1"> |
|
|
|
<strong>{t("Output Qty")}:</strong> {processData?.outputQty+" "+"("+processData?.outputQtyUom +")"} |
|
|
|
</Typography> |
|
|
|
<Box> |
|
|
|
<strong>{t("Status")}:</strong>{" "} |
|
|
|
<Chip |
|
|
|
label={t(processData?.status || 'pending')} |
|
|
|
color={processData?.status === 'completed' ? 'success' : 'primary'} |
|
|
|
label={ |
|
|
|
processData?.status === 'completed' ? t("Completed") : processData?.status === 'IN_PROGRESS' ? t("In Progress") : processData?.status === 'pending' ? t("Pending") : t("Unknown") |
|
|
|
} |
|
|
|
color={processData?.status === 'completed' ? 'success' : processData?.status === 'IN_PROGRESS' ? 'success' : processData?.status === 'pending' ? 'primary' : 'error'} |
|
|
|
size="small" |
|
|
|
/> |
|
|
|
</Typography> |
|
|
|
</Box> |
|
|
|
<Typography variant="subtitle1"> |
|
|
|
<strong>{t("Date")}:</strong> {dayjs(processData?.date).format(OUTPUT_DATE_FORMAT)} |
|
|
|
</Typography> |
|
|
|
@@ -376,103 +458,151 @@ const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({ |
|
|
|
<TableCell align="center">{t("Action")}</TableCell> |
|
|
|
</TableRow> |
|
|
|
</TableHead> |
|
|
|
<TableBody> |
|
|
|
{lines.map((line) => { |
|
|
|
const status = (line as any).status || ''; |
|
|
|
const statusLower = status.toLowerCase(); |
|
|
|
const equipmentName = (line as any).equipment_name || line.equipmentType || "-"; |
|
|
|
|
|
|
|
// 使用 status 字段判断状态 |
|
|
|
const isCompleted = statusLower === 'completed'; |
|
|
|
const isInProgress = statusLower === 'in_progress' || statusLower === 'in progress'; |
|
|
|
const isPending = statusLower === 'pending' || status === ''; |
|
|
|
|
|
|
|
return ( |
|
|
|
<TableRow key={line.id}> |
|
|
|
<TableCell>{line.seqNo}</TableCell> |
|
|
|
<TableCell> |
|
|
|
<Typography fontWeight={500}>{line.name}</Typography> |
|
|
|
</TableCell> |
|
|
|
<TableCell>{line.description || "-"}</TableCell> |
|
|
|
<TableCell>{equipmentName}</TableCell> |
|
|
|
<TableCell align="center"> |
|
|
|
{isCompleted ? ( |
|
|
|
<Chip label={t("Completed")} color="success" size="small" /> |
|
|
|
) : isInProgress ? ( |
|
|
|
<Chip label={t("In Progress")} color="primary" size="small" /> |
|
|
|
) : ( |
|
|
|
<Chip label={t("Pending")} color="default" size="small" /> |
|
|
|
)} |
|
|
|
</TableCell> |
|
|
|
<TableCell align="center"> |
|
|
|
{statusLower === 'pending' ? ( |
|
|
|
<Button |
|
|
|
variant="contained" |
|
|
|
size="small" |
|
|
|
startIcon={<PlayArrowIcon />} |
|
|
|
onClick={async () => { |
|
|
|
await handleStartLine(line.id); |
|
|
|
setSelectedLineId(line.id); |
|
|
|
setIsExecutingLine(true); |
|
|
|
await fetchProcessDetail(); |
|
|
|
}} |
|
|
|
> |
|
|
|
{t("Start")} |
|
|
|
</Button> |
|
|
|
): |
|
|
|
statusLower === 'in_progress' || statusLower === 'in progress' ? ( |
|
|
|
<Button |
|
|
|
variant="contained" |
|
|
|
size="small" |
|
|
|
startIcon={<CheckCircleIcon />} |
|
|
|
onClick={async () => { |
|
|
|
setSelectedLineId(line.id); |
|
|
|
setIsExecutingLine(true); |
|
|
|
await fetchProcessDetail(); |
|
|
|
}} |
|
|
|
> |
|
|
|
{t("View")} |
|
|
|
</Button> |
|
|
|
):( |
|
|
|
<Button |
|
|
|
variant="outlined" |
|
|
|
size="small" |
|
|
|
onClick={async() => { |
|
|
|
setSelectedLineId(line.id); |
|
|
|
setIsExecutingLine(true); |
|
|
|
await fetchProcessDetail(); |
|
|
|
}} |
|
|
|
> |
|
|
|
{t("View")} |
|
|
|
</Button> |
|
|
|
)} |
|
|
|
</TableCell> |
|
|
|
</TableRow> |
|
|
|
);})} |
|
|
|
<TableBody> |
|
|
|
{lines.map((line) => { |
|
|
|
const status = (line as any).status || ''; |
|
|
|
const statusLower = status.toLowerCase(); |
|
|
|
const equipmentName = (line as any).equipment_name || line.equipmentType || "-"; |
|
|
|
|
|
|
|
const isCompleted = statusLower === 'completed'; |
|
|
|
const isInProgress = statusLower === 'inprogress' || statusLower === 'in progress'; |
|
|
|
const isPending = statusLower === 'pending' || status === ''; |
|
|
|
|
|
|
|
return ( |
|
|
|
<TableRow key={line.id}> |
|
|
|
<TableCell>{line.seqNo}</TableCell> |
|
|
|
<TableCell> |
|
|
|
<Typography fontWeight={500}>{line.name}</Typography> |
|
|
|
</TableCell> |
|
|
|
<TableCell><Typography fontWeight={500}>{line.description || "-"}</Typography></TableCell> |
|
|
|
<TableCell><Typography fontWeight={500}>{equipmentName}</Typography></TableCell> |
|
|
|
<TableCell align="center"> |
|
|
|
{isCompleted ? ( |
|
|
|
<Chip label={t("Completed")} color="success" size="small" /> |
|
|
|
) : isInProgress ? ( |
|
|
|
<Chip label={t("In Progress")} color="primary" size="small" /> |
|
|
|
) : isPending ? ( |
|
|
|
<Chip label={t("Pending")} color="default" size="small" /> |
|
|
|
) : ( |
|
|
|
<Chip label={t("Unknown")} color="error" size="small" /> |
|
|
|
)} |
|
|
|
</TableCell> |
|
|
|
<TableCell align="center"> |
|
|
|
{statusLower === 'pending' ? ( |
|
|
|
<Button |
|
|
|
variant="contained" |
|
|
|
size="small" |
|
|
|
startIcon={<PlayArrowIcon />} |
|
|
|
onClick={() => handleStartLineWithScan(line.id)} |
|
|
|
> |
|
|
|
{t("Start")} |
|
|
|
</Button> |
|
|
|
) : statusLower === 'in_progress' || statusLower === 'in progress' ? ( |
|
|
|
<Button |
|
|
|
variant="contained" |
|
|
|
size="small" |
|
|
|
startIcon={<CheckCircleIcon />} |
|
|
|
onClick={async () => { |
|
|
|
setSelectedLineId(line.id); |
|
|
|
setIsExecutingLine(true); |
|
|
|
await fetchProcessDetail(); |
|
|
|
}} |
|
|
|
> |
|
|
|
{t("View")} |
|
|
|
</Button> |
|
|
|
) : ( |
|
|
|
<Button |
|
|
|
variant="outlined" |
|
|
|
size="small" |
|
|
|
onClick={async() => { |
|
|
|
setSelectedLineId(line.id); |
|
|
|
setIsExecutingLine(true); |
|
|
|
await fetchProcessDetail(); |
|
|
|
}} |
|
|
|
> |
|
|
|
{t("View")} |
|
|
|
</Button> |
|
|
|
)} |
|
|
|
</TableCell> |
|
|
|
</TableRow> |
|
|
|
); |
|
|
|
})} |
|
|
|
</TableBody> |
|
|
|
</Table> |
|
|
|
</TableContainer> |
|
|
|
) : ( |
|
|
|
/* ========== 步骤执行视图 ========== */ |
|
|
|
<ProductionProcessStepExecution |
|
|
|
selectedLine={selectedLine} |
|
|
|
scannedOperators={scannedOperators} |
|
|
|
scannedMachines={scannedMachines} |
|
|
|
isManualScanning={isManualScanning} |
|
|
|
outputData={outputData} |
|
|
|
onStartScan={handleStartScan} |
|
|
|
onStopScan={handleStopScan} |
|
|
|
onCancel={() => { |
|
|
|
setIsExecutingLine(false); |
|
|
|
setSelectedLineId(null); |
|
|
|
handleStopScan(); |
|
|
|
fetchProcessDetail(); |
|
|
|
}} |
|
|
|
onSubmitOutput={handleSubmitOutput} |
|
|
|
onOutputDataChange={(data) => setOutputData({...outputData, ...data})} |
|
|
|
/> |
|
|
|
lineId={selectedLineId} |
|
|
|
//onClose={() => { |
|
|
|
// setIsExecutingLine(false) |
|
|
|
// setSelectedLineId(null) |
|
|
|
//}} |
|
|
|
//onOutputSubmitted={async () => { |
|
|
|
// await fetchProcessDetail() |
|
|
|
//}} |
|
|
|
/> |
|
|
|
)} |
|
|
|
</Paper> |
|
|
|
|
|
|
|
{/* QR 扫描对话框 */} |
|
|
|
<Dialog |
|
|
|
open={showScanDialog} |
|
|
|
onClose={() => { |
|
|
|
handleStopScan(); |
|
|
|
setShowScanDialog(false); |
|
|
|
}} |
|
|
|
maxWidth="sm" |
|
|
|
fullWidth |
|
|
|
> |
|
|
|
<DialogTitle>{t("Scan Operator & Equipment")}</DialogTitle> |
|
|
|
<DialogContent> |
|
|
|
<Stack spacing={2} sx={{ mt: 2 }}> |
|
|
|
<Box> |
|
|
|
<Typography variant="body2" color="text.secondary"> |
|
|
|
{scannedOperatorId |
|
|
|
? `${t("Operator")}: ${scannedOperatorId}` |
|
|
|
: t("Please scan operator code") |
|
|
|
} |
|
|
|
</Typography> |
|
|
|
</Box> |
|
|
|
|
|
|
|
<Box> |
|
|
|
<Typography variant="body2" color="text.secondary"> |
|
|
|
{scannedEquipmentId |
|
|
|
? `${t("Equipment")}: ${scannedEquipmentId}` |
|
|
|
: t("Please scan equipment code (optional if not required)") |
|
|
|
} |
|
|
|
</Typography> |
|
|
|
</Box> |
|
|
|
|
|
|
|
<Button |
|
|
|
variant={isManualScanning ? "outlined" : "contained"} |
|
|
|
startIcon={<QrCodeIcon />} |
|
|
|
onClick={isManualScanning ? handleStopScan : () => scanningLineId && handleStartScan(scanningLineId)} |
|
|
|
color={isManualScanning ? "secondary" : "primary"} |
|
|
|
fullWidth |
|
|
|
> |
|
|
|
{isManualScanning ? t("Stop QR Scan") : t("Start QR Scan")} |
|
|
|
</Button> |
|
|
|
</Stack> |
|
|
|
</DialogContent> |
|
|
|
<DialogActions> |
|
|
|
<Button onClick={() => { |
|
|
|
handleStopScan(); |
|
|
|
setShowScanDialog(false); |
|
|
|
}}> |
|
|
|
{t("Cancel")} |
|
|
|
</Button> |
|
|
|
<Button |
|
|
|
variant="contained" |
|
|
|
onClick={() => scanningLineId && handleSubmitScanAndStart(scanningLineId)} |
|
|
|
disabled={!scannedOperatorId} |
|
|
|
> |
|
|
|
{t("Submit & Start")} |
|
|
|
</Button> |
|
|
|
</DialogActions> |
|
|
|
</Dialog> |
|
|
|
</Box> |
|
|
|
); |
|
|
|
}; |
|
|
|
|