|
- "use client";
- import React, { useCallback, useEffect, useState, useRef } from "react";
- import EditIcon from "@mui/icons-material/Edit";
- import AddIcon from '@mui/icons-material/Add';
- import DeleteIcon from '@mui/icons-material/Delete';
- import Fab from '@mui/material/Fab';
- import {
- Box,
- Button,
- Paper,
- Stack,
- Typography,
- TextField,
- Table,
- TableBody,
- TableCell,
- TableContainer,
- TableHead,
- TableRow,
- Chip,
- Card,
- CardContent,
- CircularProgress,
- Dialog,
- DialogTitle,
- DialogContent,
- DialogActions,
- IconButton
- } 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,
- ProductProcessWithLinesResponse, // 添加
- ProductProcessLineResponse,
- passProductProcessLine,
- newProductProcessLine,
- updateProductProcessLineProcessingTimeSetupTimeChangeoverTime,
- UpdateProductProcessLineProcessingTimeSetupTimeChangeoverTimeRequest,
- deleteProductProcessLine,
- } from "@/app/api/jo/actions";
- import { updateProductProcessLineStatus } 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<ProductProcessDetailProps> = ({
- jobOrderId,
- onBack,
- fromJosave,
- }) => {
- console.log(" ProductionProcessDetail RENDER", { jobOrderId, 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<ProductProcessWithLinesResponse | null>(null); // 修改类型
- const [lines, setLines] = useState<ProductProcessLineResponse[]>([]); // 修改类型
- const [loading, setLoading] = useState(false);
- const linesRef = useRef<ProductProcessLineResponse[]>([]);
- const onBackRef = useRef(onBack);
- const fetchProcessDetailRef = useRef<() => Promise<void>>();
-
- // 选中的 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 [scannedOperatorId, setScannedOperatorId] = useState<number | null>(null);
- const [scannedEquipmentId, setScannedEquipmentId] = useState<number | null>(null);
- // const [scannedEquipmentTypeSubTypeEquipmentNo, setScannedEquipmentTypeSubTypeEquipmentNo] = useState<string | null>(null);
- const [scannedStaffNo, setScannedStaffNo] = useState<string | null>(null);
- // const [scannedEquipmentDetailId, setScannedEquipmentDetailId] = useState<number | null>(null);
- const [scannedEquipmentCode, setScannedEquipmentCode] = useState<string | null>(null);
- const [scanningLineId, setScanningLineId] = useState<number | null>(null);
- const [lineDetailForScan, setLineDetailForScan] = useState<JobOrderProcessLineDetailResponse | null>(null);
- const [showScanDialog, setShowScanDialog] = useState(false);
- const autoSubmitTimerRef = useRef<NodeJS.Timeout | null>(null);
- const [openTimeDialog, setOpenTimeDialog] = useState(false);
- const [editingLineId, setEditingLineId] = useState<number | null>(null);
- const [timeValues, setTimeValues] = useState({
- durationInMinutes: 0,
- prepTimeInMinutes: 0,
- postProdTimeInMinutes: 0,
- });
-
- 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);
- };
- useEffect(() => {
- onBackRef.current = onBack;
- }, [onBack]);
- // 获取 process 和 lines 数据
- const fetchProcessDetail = useCallback(async () => {
- console.log(" fetchProcessDetail CALLED", { jobOrderId, timestamp: new Date().toISOString() });
- setLoading(true);
- try {
- console.log(` Loading process detail for JobOrderId: ${jobOrderId}`);
-
- const processesWithLines = await fetchProductProcessesByJobOrderId(jobOrderId);
-
- if (!processesWithLines || processesWithLines.length === 0) {
- throw new Error("No processes found for this job order");
- }
-
- const currentProcess = processesWithLines[0];
- setProcessData(currentProcess);
-
- const lines = currentProcess.productProcessLines || [];
- setLines(lines);
- linesRef.current = lines;
- console.log(" Process data loaded:", currentProcess);
- console.log(" Lines loaded:", lines);
- } catch (error) {
- console.error(" Error loading process detail:", error);
- onBackRef.current();
- } finally {
- setLoading(false);
- }
- }, [jobOrderId]);
-
- const handleOpenTimeDialog = useCallback((lineId: number) => {
- console.log("🔓 handleOpenTimeDialog CALLED", { lineId, timestamp: new Date().toISOString() });
-
- // 直接使用 linesRef.current,避免触发 setLines
- const line = linesRef.current.find(l => l.id === lineId);
- if (line) {
- console.log(" Found line:", line);
- setEditingLineId(lineId);
- setTimeValues({
- durationInMinutes: line.durationInMinutes || 0,
- prepTimeInMinutes: line.prepTimeInMinutes || 0,
- postProdTimeInMinutes: line.postProdTimeInMinutes || 0,
- });
- setOpenTimeDialog(true);
- console.log(" Dialog opened");
- } else {
- console.warn(" Line not found:", lineId);
- }
- }, []);
- useEffect(() => {
- fetchProcessDetailRef.current = fetchProcessDetail;
- }, [fetchProcessDetail]);
- const handleCloseTimeDialog = useCallback(() => {
- console.log("🔒 handleCloseTimeDialog CALLED", { timestamp: new Date().toISOString() });
- setOpenTimeDialog(false);
- setEditingLineId(null);
- setTimeValues({
- durationInMinutes: 0,
- prepTimeInMinutes: 0,
- postProdTimeInMinutes: 0,
- });
- console.log(" Dialog closed");
- }, []);
-
- const handleConfirmTimeUpdate = useCallback(async () => {
- console.log("💾 handleConfirmTimeUpdate CALLED", { editingLineId, timeValues, timestamp: new Date().toISOString() });
- if (!editingLineId) return;
-
- try {
- const request: UpdateProductProcessLineProcessingTimeSetupTimeChangeoverTimeRequest = {
- productProcessLineId: editingLineId,
- processingTime: timeValues.durationInMinutes,
- setupTime: timeValues.prepTimeInMinutes,
- changeoverTime: timeValues.postProdTimeInMinutes,
- };
-
- await updateProductProcessLineProcessingTimeSetupTimeChangeoverTime(editingLineId, request);
- await fetchProcessDetail();
- handleCloseTimeDialog();
- } catch (error) {
- console.error("Error updating time:", error);
- alert(t("update failed"));
- }
- }, [editingLineId, timeValues, fetchProcessDetail, handleCloseTimeDialog, t]);
-
- useEffect(() => {
- console.log("🔄 useEffect [jobOrderId] TRIGGERED", {
- jobOrderId,
- timestamp: new Date().toISOString()
- });
- if (fetchProcessDetailRef.current) {
- fetchProcessDetailRef.current();
- }
- }, [jobOrderId]);
-
- // 添加监听 openTimeDialog 变化的 useEffect
- useEffect(() => {
- console.log(" openTimeDialog changed:", { openTimeDialog, timestamp: new Date().toISOString() });
- }, [openTimeDialog]);
-
- // 添加监听 timeValues 变化的 useEffect
- useEffect(() => {
- console.log(" timeValues changed:", { timeValues, timestamp: new Date().toISOString() });
- }, [timeValues]);
-
- // 添加监听 lines 变化的 useEffect
- useEffect(() => {
- console.log(" lines changed:", { count: lines.length, lines, timestamp: new Date().toISOString() });
- }, [lines]);
-
- // 添加监听 editingLineId 变化的 useEffect
- useEffect(() => {
- console.log(" editingLineId changed:", { editingLineId, timestamp: new Date().toISOString() });
- }, [editingLineId]);
-
- const handlePassLine = useCallback(async (lineId: number) => {
- try {
- await passProductProcessLine(lineId);
- // 刷新数据
- await fetchProcessDetail();
- } catch (error) {
- console.error("Error passing line:", error);
- alert(t("Failed to pass line. Please try again."));
- }
- }, [fetchProcessDetail, t]);
- const handleCreateNewLine = useCallback(async (lineId: number) => {
- try {
- await newProductProcessLine(lineId);
- // 刷新数据
- await fetchProcessDetail();
- } catch (error) {
- console.error("Error creating new line:", error);
- alert(t("Failed to create new line. Please try again."));
- }
- }, [fetchProcessDetail, t]);
- const handleDeleteLine = useCallback(async (lineId: number) => {
- if (!confirm(t("Are you sure you want to delete this process?"))) {
- return;
- }
- try {
- await deleteProductProcessLine(lineId);
- // 刷新数据
- await fetchProcessDetail();
- } catch (error) {
- console.error("Error deleting line:", error);
- alert(t("Failed to delete line. Please try again."));
- }
- }, [fetchProcessDetail, t]);
- const processQrCode = useCallback((qrValue: string, lineId: number) => {
- // 设备快捷格式:{2fitesteXXX} - XXX 直接作为设备代码
- // 格式:{2fitesteXXX} = equipmentCode: "XXX"
- // 例如:{2fiteste包裝機類-真空八爪魚機-1號} = equipmentCode: "包裝機類-真空八爪魚機-1號"
- if (qrValue.match(/\{2fiteste(.+)\}/)) {
- const match = qrValue.match(/\{2fiteste(.+)\}/);
- const equipmentCode = match![1];
- setScannedEquipmentCode(equipmentCode);
- console.log(`Set equipmentCode from shortcut: ${equipmentCode}`);
- 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);
-
- // 添加组件卸载日志
- useEffect(() => {
- return () => {
- console.log("🗑️ ProductionProcessDetail UNMOUNTING");
- };
- }, []);
-
- if (loading) {
- return (
- <Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
- <CircularProgress/>
- </Box>
- );
- }
-
- return (
- <Box>
- {/* ========== 第二部分:Process Lines ========== */}
- <Paper sx={{ p: 3 }}>
- <Typography variant="h6" gutterBottom fontWeight="bold">
- {t("Production Process Steps")}
- </Typography>
- <ProcessSummaryHeader processData={processData} />
- {!isExecutingLine ? (
- /* ========== 步骤列表视图 ========== */
- <TableContainer>
- <Table>
- <TableHead>
- <TableRow>
- <TableCell>{t(" ")}</TableCell>
- <TableCell>{t("Seq")}</TableCell>
- <TableCell>{t("Step Name")}</TableCell>
- <TableCell>{t("Description")}</TableCell>
- <TableCell>{t("EquipmentType-EquipmentName-Code")}</TableCell>
- <TableCell>{t("Operator")}</TableCell>
- <TableCell>{t("Assume End Time")}</TableCell>
- <TableCell>
- <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
- <Typography variant="body2" sx={{ fontWeight: 500 }}>
- {t("Time Information(mins)")}
- </Typography>
- </Box>
- </TableCell>
- <TableCell align="center">{t("Status")}</TableCell>
-
- {!fromJosave&&(<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.equipment_name || "-";
- const isPlanning = processData?.jobOrderStatus === "planning";
- const isCompleted = statusLower === 'completed';
- const isInProgress = statusLower === 'inprogress' || statusLower === 'in progress';
- const isPaused = statusLower === 'paused';
- const isPending = statusLower === 'pending' || status === '';
- const isPass = statusLower === 'pass';
- const isPassDisabled = isCompleted || isPass;
- return (
- <TableRow key={line.id}>
- <TableCell>
- {isPlanning && (
- <Fab
- size="small"
- color="primary"
- aria-label={t("Create New Line")}
- onClick={() => handleCreateNewLine(line.id)}
- sx={{
- width: 32,
- height: 32,
- minHeight: 32,
- boxShadow: 1,
- '&:hover': { boxShadow: 3 },
- }}
- >
- <AddIcon fontSize="small" />
- </Fab>
- )}
- {isPlanning && line.isOringinal !== true && (
- <IconButton
- size="small"
- color="error"
- onClick={() => handleDeleteLine(line.id)}
- sx={{ padding: 0.5 }}
- >
- <DeleteIcon fontSize="small" />
- </IconButton>
- )}
- </TableCell>
- <TableCell>
- <Stack direction="row" spacing={1} alignItems="center">
- <Typography variant="body2" textAlign="center">{line.seqNo}</Typography>
- </Stack>
- </TableCell>
- <TableCell>
- <Typography variant="body2" fontWeight={500}>{line.name}</Typography>
- </TableCell>
- <TableCell>
- <Typography variant="body2" fontWeight={500} maxWidth={200} sx={{ wordBreak: 'break-word', whiteSpace: 'normal', lineHeight: 1.5 }}>{line.description || "-"}</Typography>
- </TableCell>
- <TableCell>
- <Typography variant="body2" fontWeight={500}>{line.equipmentDetailCode||equipmentName}</Typography>
- </TableCell>
- <TableCell>
- <Typography variant="body2" fontWeight={500}>{line.operatorName}</Typography>
- </TableCell>
- <TableCell>
- <Typography variant="body2" fontWeight={500}>
- {line.startTime && line.durationInMinutes
- ? dayjs(line.startTime)
- .add(line.durationInMinutes, 'minute')
- .format('MM-DD HH:mm')
- : '-'}
- </Typography>
- </TableCell>
- <TableCell>
- <Box sx={{ display: 'flex', flexDirection: 'column', gap: 0.5 }}>
- <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
- <Typography variant="body2">
- {t("Processing Time")}: {line.durationInMinutes || 0}{t("mins")}
- </Typography>
- {processData?.jobOrderStatus === "planning" && (
- <IconButton
- size="small"
- onClick={() => {
- console.log("🖱️ Edit button clicked for line:", line.id);
- handleOpenTimeDialog(line.id);
- }}
- sx={{ padding: 0.5 }}
- >
- <EditIcon fontSize="small" />
- </IconButton>
- )}
- </Box>
- <Typography variant="body2">
- {t("Setup Time")}: {line.prepTimeInMinutes || 0} {t("mins")}
- </Typography>
- <Typography variant="body2">
- {t("Changeover Time")}: {line.postProdTimeInMinutes || 0} {t("mins")}
- </Typography>
- </Box>
- </TableCell>
- <TableCell align="center">
- {isCompleted ? (
- <Chip label={t("Completed")} color="success" size="small"
- onClick={async () => {
- setSelectedLineId(line.id);
- setShowOutputPage(false);
- setIsExecutingLine(true);
- await fetchProcessDetail();
- }}
- />
- ) : isInProgress ? (
- <Chip label={t("In Progress")} color="primary" size="small"
- onClick={async () => {
- setSelectedLineId(line.id);
- setShowOutputPage(false);
- setIsExecutingLine(true);
- await fetchProcessDetail();
- }} />
- ) : isPending ? (
- <Chip label={t("Pending")} color="default" size="small" />
- ) : isPaused ? (
- <Chip label={t("Paused")} color="warning" size="small" />
- ) : isPass ? (
- <Chip label={t("Pass")} color="success" size="small" />
- ) : (
- <Chip label={t("Unknown")} color="error" size="small" />
- )
- }
- </TableCell>
- {!fromJosave&&(
- <TableCell align="center">
- <Stack direction="row" spacing={1} justifyContent="center">
- {statusLower === 'pending' ? (
- <>
- <Button
- variant="contained"
- size="small"
- startIcon={<PlayArrowIcon />}
- onClick={() => handleStartLineWithScan(line.id)}
- >
- {t("Start")}
- </Button>
- <Button
- variant="outlined"
- size="small"
- color="success"
- onClick={() => handlePassLine(line.id)}
- disabled={isPassDisabled}
- >
- {t("Pass")}
- </Button>
- </>
- ) : statusLower === 'in_progress' || statusLower === 'in progress' || statusLower === 'paused' ? (
- <>
- <Button
- variant="contained"
- size="small"
- startIcon={<CheckCircleIcon />}
- onClick={async () => {
- setSelectedLineId(line.id);
- setShowOutputPage(false);
- setIsExecutingLine(true);
- await fetchProcessDetail();
- }}
- >
- {t("View")}
- </Button>
- <Button
- variant="outlined"
- size="small"
- color="success"
- onClick={() => handlePassLine(line.id)}
- disabled={isPassDisabled}
- >
- {t("Pass")}
- </Button>
- </>
- ) : (
- <>
- <Button
- variant="outlined"
- size="small"
- onClick={async() => {
- setSelectedLineId(line.id);
- setIsExecutingLine(true);
- await fetchProcessDetail();
- }}
- >
- {t("View")}
- </Button>
- <Button
- variant="outlined"
- size="small"
- color="success"
- onClick={() => handlePassLine(line.id)}
- disabled={isPassDisabled}
- >
- {t("Pass")}
- </Button>
- </>
- )}
- </Stack>
- </TableCell>
- )}
- </TableRow>
- );
- })}
- </TableBody>
- </Table>
- </TableContainer>
- ) : (
- /* ========== 步骤执行视图 ========== */
- <ProductionProcessStepExecution
- lineId={selectedLineId}
- onBack={handleBackFromStep}
- processData={processData} // 添加
- allLines={lines} // 添加
- jobOrderId={jobOrderId} // 添加
- />
- )}
- </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">
- {scannedStaffNo
- ? `${t("Staff No")}: ${scannedStaffNo}`
- : t("Please scan staff no")
- }
- </Typography>
- </Box>
-
- <Box>
- <Typography variant="body2" color="text.secondary">
- {scannedEquipmentCode
- ? `${t("Equipment Code")}: ${scannedEquipmentCode}`
- : t("Please scan equipment code")
- }
- </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 type="button" onClick={() => {
- handleStopScan();
- setShowScanDialog(false);
- }}>
- {t("Cancel")}
- </Button>
- <Button
- type="button"
- variant="contained"
- onClick={() => scanningLineId && handleSubmitScanAndStart(scanningLineId)}
- disabled={!scannedStaffNo }
- >
- {t("Submit & Start")}
- </Button>
- </DialogActions>
- </Dialog>
- <Dialog
- open={openTimeDialog}
- onClose={handleCloseTimeDialog} // 直接传递函数,不要包装
- fullWidth
- maxWidth="sm"
- >
- <DialogTitle>{t("Update Time Information")}</DialogTitle>
- <DialogContent>
- <Stack spacing={2} sx={{ mt: 1 }}>
- <TextField
- label={t("Processing Time (mins)")}
- type="number"
- fullWidth
- value={timeValues.durationInMinutes}
- onChange={(e) => {
- console.log("⌨️ Processing Time onChange:", {
- value: e.target.value,
- openTimeDialog,
- editingLineId,
- timestamp: new Date().toISOString()
- });
- const value = e.target.value === '' ? 0 : parseInt(e.target.value) || 0;
- setTimeValues(prev => ({
- ...prev,
- durationInMinutes: Math.max(0, value)
- }));
- }}
- inputProps={{
- min: 0,
- step: 1
- }}
- />
- <TextField
- label={t("Setup Time (mins)")}
- type="number"
- fullWidth
- value={timeValues.prepTimeInMinutes}
- onChange={(e) => {
- console.log("⌨️ Setup Time onChange:", {
- value: e.target.value,
- openTimeDialog,
- editingLineId,
- timestamp: new Date().toISOString()
- });
- const value = e.target.value === '' ? 0 : parseInt(e.target.value) || 0;
- setTimeValues(prev => ({
- ...prev,
- prepTimeInMinutes: Math.max(0, value)
- }));
- }}
- inputProps={{
- min: 0,
- step: 1
- }}
- />
- <TextField
- label={t("Changeover Time (mins)")}
- type="number"
- fullWidth
- value={timeValues.postProdTimeInMinutes}
- onChange={(e) => {
- console.log("⌨️ Changeover Time onChange:", {
- value: e.target.value,
- openTimeDialog,
- editingLineId,
- timestamp: new Date().toISOString()
- });
- const value = e.target.value === '' ? 0 : parseInt(e.target.value) || 0;
- setTimeValues(prev => ({
- ...prev,
- postProdTimeInMinutes: Math.max(0, value)
- }));
- }}
- inputProps={{
- min: 0,
- step: 1
- }}
- />
- </Stack>
- </DialogContent>
- <DialogActions>
- <Button
- type="button"
- onClick={handleCloseTimeDialog}
- >
- {t("Cancel")}
- </Button>
- <Button
- type="button"
- variant="contained"
- onClick={handleConfirmTimeUpdate}
- >
- {t("Save")}
- </Button>
- </DialogActions>
- </Dialog>
- </Box>
- );
- };
-
- export default ProductionProcessDetail;
|