|
- "use client";
- import {
- Box,
- Button,
- Paper,
- Stack,
- Typography,
- TextField,
- Table,
- TableBody,
- TableCell,
- TableHead,
- TableRow,
- Dialog,
- DialogTitle,
- DialogContent,
- DialogActions,
- Card,
- CardContent,
- Grid,
- } from "@mui/material";
- import { Alert } from "@mui/material";
-
- import QrCodeIcon from '@mui/icons-material/QrCode';
- import CheckCircleIcon from "@mui/icons-material/CheckCircle";
- import StopIcon from "@mui/icons-material/Stop";
- import PauseIcon from "@mui/icons-material/Pause";
- import PlayArrowIcon from "@mui/icons-material/PlayArrow";
- import { useTranslation } from "react-i18next";
- import {
- JobOrderProcessLineDetailResponse,
- updateProductProcessLineQty,
- updateProductProcessLineQrscan,
- fetchProductProcessLineDetail,
- UpdateProductProcessLineQtyRequest,
- saveProductProcessResumeTime,
- saveProductProcessIssueTime,
- ProductProcessWithLinesResponse, // ✅ 添加
- ProductProcessLineResponse, // ✅ 添加
- } from "@/app/api/jo/actions";
- import { Operator, Machine } from "@/app/api/jo";
- import React, { useCallback, useEffect, useState, useMemo } from "react"; // ✅ 添加 useMemo
- import { useQrCodeScannerContext } from "../QrCodeScannerProvider/QrCodeScannerProvider";
- import { fetchNameList, NameList } from "@/app/api/user/actions";
- import BagConsumptionForm from "./BagConsumptionForm"; // ✅ 添加导入
- import OverallTimeRemainingCard from "./OverallTimeRemainingCard"; // ✅ 添加导入
- import dayjs from "dayjs";
- interface ProductionProcessStepExecutionProps {
- lineId: number | null;
- onBack: () => void;
- processData?: ProductProcessWithLinesResponse | null; // ✅ 添加
- allLines?: ProductProcessLineResponse[]; // ✅ 添加
- jobOrderId?: number; // ✅ 添加
- }
-
- const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionProps> = ({
- lineId,
- onBack,
- processData, // ✅ 添加
- allLines, // ✅ 添加
- jobOrderId, // ✅ 添加
- }) => {
- const { t } = useTranslation( ["common","jo"]);
- const [lineDetail, setLineDetail] = useState<JobOrderProcessLineDetailResponse | null>(null);
- const isCompleted = lineDetail?.status === "Completed" || lineDetail?.status === "Pass";
- const [outputData, setOutputData] = useState<UpdateProductProcessLineQtyRequest & {
- byproductName: string;
- byproductQty: number;
- byproductUom: string;
- }>({
- productProcessLineId: lineId ?? 0,
- outputFromProcessQty: 0,
- outputFromProcessUom: "",
- defectQty: 0,
- defectUom: "",
- scrapQty: 0,
- scrapUom: "",
- byproductName: "",
- byproductQty: 0,
- byproductUom: "",
- defect2Qty: 0,
- defect2Uom: "",
- defect3Qty: 0,
- defect3Uom: "",
- defectDescription: "",
- defectDescription2: "",
- defectDescription3: ""
- });
- const [isManualScanning, setIsManualScanning] = useState(false);
- const [processedQrCodes, setProcessedQrCodes] = useState<Set<string>>(new Set());
- const [scannedOperators, setScannedOperators] = useState<Operator[]>([]);
- const [scannedMachines, setScannedMachines] = useState<Machine[]>([]);
- const [isPaused, setIsPaused] = useState(false);
- const [showOutputTable, setShowOutputTable] = useState(false);
- const { values: qrValues, startScan, stopScan, resetScan } = useQrCodeScannerContext();
- const equipmentName = (lineDetail as any)?.equipment || lineDetail?.equipmentType || "-";
- const [remainingTime, setRemainingTime] = useState<string | null>(null);
- const [isOverTime, setIsOverTime] = useState(false);
- const [frozenRemainingTime, setFrozenRemainingTime] = useState<string | null>(null);
- const [lastPauseTime, setLastPauseTime] = useState<Date | null>(null);
- const[isOpenReasonModel, setIsOpenReasonModel] = useState(false);
- const [pauseReason, setPauseReason] = useState("");
-
- // ✅ 添加:判断是否显示 Bag 表单的条件
- const shouldShowBagForm = useMemo(() => {
- if (!processData || !allLines || !lineDetail) return false;
-
- // 检查 BOM description 是否为 "FG"
- const bomDescription = processData.bomDescription;
- if (bomDescription !== "FG") return false;
-
- // 检查是否是最后一个 process line(按 seqNo 排序)
- const sortedLines = [...allLines].sort((a, b) => (a.seqNo || 0) - (b.seqNo || 0));
- const maxSeqNo = sortedLines[sortedLines.length - 1]?.seqNo;
- const isLastLine = lineDetail.seqNo === maxSeqNo;
-
- return isLastLine;
- }, [processData, allLines, lineDetail]);
-
- // ✅ 添加:刷新 line detail 的函数
- const handleRefreshLineDetail = useCallback(async () => {
- if (lineId) {
- try {
- const detail = await fetchProductProcessLineDetail(lineId);
- setLineDetail(detail as any);
- } catch (error) {
- console.error("Failed to refresh line detail", error);
- }
- }
- }, [lineId]);
-
- useEffect(() => {
- if (!lineId) {
- setLineDetail(null);
- return;
- }
- fetchProductProcessLineDetail(lineId)
- .then((detail) => {
- setLineDetail(detail as any);
- console.log("📋 Line Detail loaded:", {
- id: detail.id,
- status: detail.status,
- durationInMinutes: detail.durationInMinutes,
- startTime: detail.startTime,
- startTimeType: typeof detail.startTime,
- hasDuration: !!detail.durationInMinutes,
- hasStartTime: !!detail.startTime,
- });
- setOutputData(prev => ({
- ...prev,
- productProcessLineId: detail.id,
- outputFromProcessQty: (detail as any).outputFromProcessQty || 0,
- outputFromProcessUom: (detail as any).outputFromProcessUom || "",
- defectQty: detail.defectQty || 0,
- defectUom: detail.defectUom || "",
- scrapQty: detail.scrapQty || 0,
- scrapUom: detail.scrapUom || "",
- byproductName: detail.byproductName || "",
- byproductQty: detail.byproductQty || 0,
- byproductUom: detail.byproductUom || ""
- }));
- })
- .catch(err => {
- console.error("Failed to load line detail", err);
- setLineDetail(null);
- });
- }, [lineId]);
-
- useEffect(() => {
- // Don't show time remaining if completed
- if (lineDetail?.status === "Completed" || lineDetail?.status === "Pass") {
- console.log("Line is completed");
- setRemainingTime(null);
- setIsOverTime(false);
- return;
- }
-
- console.log("🔍 Time Remaining Debug:", {
- lineId: lineDetail?.id,
- equipmentId: lineDetail?.equipmentId,
- equipmentType: lineDetail?.equipmentType,
- durationInMinutes: lineDetail?.durationInMinutes,
- startTime: lineDetail?.startTime,
- startTimeType: typeof lineDetail?.startTime,
- isStartTimeArray: Array.isArray(lineDetail?.startTime),
- status: lineDetail?.status,
- hasDuration: !!lineDetail?.durationInMinutes,
- hasStartTime: !!lineDetail?.startTime,
- });
-
- if (!lineDetail?.durationInMinutes || !lineDetail?.startTime) {
- console.log("❌ Line duration or start time is not valid", {
- durationInMinutes: lineDetail?.durationInMinutes,
- startTime: lineDetail?.startTime,
- equipmentId: lineDetail?.equipmentId,
- equipmentType: lineDetail?.equipmentType,
- });
- setRemainingTime(null);
- setIsOverTime(false);
- return;
- }
-
- let start: Date;
- if (Array.isArray(lineDetail.startTime)) {
- console.log("Line start time is an array:", lineDetail.startTime);
- const [year, month, day, hour = 0, minute = 0, second = 0] = lineDetail.startTime;
- start = new Date(year, month - 1, day, hour, minute, second);
- } else {
- start = new Date(lineDetail.startTime);
- console.log("Line start time is a string:", lineDetail.startTime);
- }
-
- if (isNaN(start.getTime())) {
- console.error("Invalid startTime:", lineDetail.startTime);
- setRemainingTime(null);
- setIsOverTime(false);
- return;
- }
-
- const durationMs = lineDetail.durationInMinutes * 60_000;
-
- const isPaused = lineDetail.status === "Paused" || lineDetail.productProcessIssueStatus === "Paused";
-
- const parseStopTime = (stopTime: string | number[] | undefined): Date | null => {
- if (!stopTime) return null;
-
- if (Array.isArray(stopTime)) {
- const [year, month, day, hour = 0, minute = 0, second = 0] = stopTime;
- return new Date(year, month - 1, day, hour, minute, second);
- } else {
- return new Date(stopTime);
- }
- };
-
- const update = () => {
- if (isPaused) {
- if (!frozenRemainingTime) {
- const pauseTime = lineDetail.stopTime
- ? parseStopTime(lineDetail.stopTime)
- : null;
-
- const pauseTimeToUse = pauseTime && !isNaN(pauseTime.getTime())
- ? pauseTime
- : new Date();
-
- const totalPausedTimeMs = (lineDetail as any).totalPausedTimeMs || 0;
-
- console.log("⏸️ Paused - calculating frozen time:", {
- stopTime: lineDetail.stopTime,
- pauseTime: pauseTimeToUse,
- startTime: start,
- totalPausedTimeMs: totalPausedTimeMs,
- });
-
- const elapsed = pauseTimeToUse.getTime() - start.getTime() - totalPausedTimeMs;
- const remaining = durationMs - elapsed;
-
- if (remaining <= 0) {
- const overTime = Math.abs(remaining);
- const minutes = Math.floor(overTime / 60000).toString().padStart(2, "0");
- const seconds = Math.floor((overTime % 60000) / 1000).toString().padStart(2, "0");
- const frozenValue = `-${minutes}:${seconds}`;
- setFrozenRemainingTime(frozenValue);
- setRemainingTime(frozenValue);
- setIsOverTime(true);
- console.log("⏸️ Frozen time (overtime):", frozenValue);
- } else {
- const minutes = Math.floor(remaining / 60000).toString().padStart(2, "0");
- const seconds = Math.floor((remaining % 60000) / 1000).toString().padStart(2, "0");
- const frozenValue = `${minutes}:${seconds}`;
- setFrozenRemainingTime(frozenValue);
- setRemainingTime(frozenValue);
- setIsOverTime(false);
- console.log("⏸️ Frozen time:", frozenValue);
- }
- } else {
- setRemainingTime(frozenRemainingTime);
- console.log("⏸️ Using frozen time:", frozenRemainingTime);
- }
- return;
- }
-
- if (frozenRemainingTime && !isPaused) {
- console.log("▶️ Resumed - clearing frozen time");
- setFrozenRemainingTime(null);
- setLastPauseTime(null);
- }
-
- const totalPausedTimeMs = (lineDetail as any).totalPausedTimeMs || 0;
- const now = new Date();
-
- const elapsed = now.getTime() - start.getTime() - totalPausedTimeMs;
- const remaining = durationMs - elapsed;
-
- console.log("⏱️ Time calculation:", {
- now: now,
- start: start,
- totalPausedTimeMs: totalPausedTimeMs,
- elapsed: elapsed,
- remaining: remaining,
- durationMs: durationMs,
- });
-
- if (remaining <= 0) {
- const overTime = Math.abs(remaining);
- const minutes = Math.floor(overTime / 60000).toString().padStart(2, "0");
- const seconds = Math.floor((overTime % 60000) / 1000).toString().padStart(2, "0");
- setRemainingTime(`-${minutes}:${seconds}`);
- setIsOverTime(true);
- } else {
- const minutes = Math.floor(remaining / 60000).toString().padStart(2, "0");
- const seconds = Math.floor((remaining % 60000) / 1000).toString().padStart(2, "0");
- setRemainingTime(`${minutes}:${seconds}`);
- setIsOverTime(false);
- }
- };
-
- update();
-
- if (!isPaused) {
- const timer = setInterval(update, 1000);
- return () => clearInterval(timer);
- }
- }, [lineDetail?.durationInMinutes, lineDetail?.startTime, lineDetail?.status, lineDetail?.productProcessIssueStatus, lineDetail?.stopTime, frozenRemainingTime]);
-
- useEffect(() => {
- const wasPaused = lineDetail?.status === "Paused" || lineDetail?.productProcessIssueStatus === "Paused";
- const isNowInProgress = lineDetail?.status === "InProgress";
-
- if (wasPaused && isNowInProgress && frozenRemainingTime) {
- setFrozenRemainingTime(null);
- }
- }, [lineDetail?.status, lineDetail?.productProcessIssueStatus]);
-
- const handleSubmitOutput = async () => {
- if (!lineDetail?.id) return;
-
- try {
- await updateProductProcessLineQty({
- productProcessLineId: lineDetail?.id || 0 as number,
- byproductName: outputData.byproductName,
- byproductQty: outputData.byproductQty,
- byproductUom: outputData.byproductUom,
- outputFromProcessQty: outputData.outputFromProcessQty,
- outputFromProcessUom: outputData.outputFromProcessUom,
- defectQty: outputData.defectQty,
- defectUom: outputData.defectUom,
- defect2Qty: outputData.defect2Qty,
- defect2Uom: outputData.defect2Uom,
- defect3Qty: outputData.defect3Qty,
- defect3Uom: outputData.defect3Uom,
- defectDescription: outputData.defectDescription,
- defectDescription2: outputData.defectDescription2,
- defectDescription3: outputData.defectDescription3,
- scrapQty: outputData.scrapQty,
- scrapUom: outputData.scrapUom,
- });
-
- console.log(" Output data submitted successfully");
-
- fetchProductProcessLineDetail(lineDetail.id)
- .then((detail) => {
- console.log("Line Detail loaded:", {
- id: detail.id,
- status: detail.status,
- startTime: detail.startTime,
- durationInMinutes: detail.durationInMinutes,
- productProcessIssueStatus: detail.productProcessIssueStatus
- });
- setLineDetail(detail as any);
- setOutputData(prev => ({
- ...prev,
- productProcessLineId: detail.id,
- outputFromProcessQty: (detail as any).outputFromProcessQty || 0,
- outputFromProcessUom: (detail as any).outputFromProcessUom || "",
- defectQty: detail.defectQty || 0,
- defectUom: detail.defectUom || "",
- defectDescription: detail.defectDescription || "",
- defectDescription2: detail.defectDescription2 || "",
- defectDescription3: detail.defectDescription3 || "",
- defectQty2: detail.defectQty2 || 0,
- defectUom2: detail.defectUom2 || "",
- defectQty3: detail.defectQty3 || 0,
- defectUom3: detail.defectUom3 || "",
- scrapQty: detail.scrapQty || 0,
- scrapUom: detail.scrapUom || "",
- byproductName: detail.byproductName || "",
- byproductQty: detail.byproductQty || 0,
- byproductUom: detail.byproductUom || ""
- }));
- })
- .catch(err => {
- console.error("Failed to load line detail", err);
- setLineDetail(null);
- });
- } catch (error) {
- console.error("Error submitting output:", error);
- alert("Failed to submit output data. Please try again.");
- }
- };
-
- useEffect(() => {
- if (isManualScanning && qrValues.length > 0 && lineDetail?.id) {
- const latestQr = qrValues[qrValues.length - 1];
-
- if (processedQrCodes.has(latestQr)) {
- return;
- }
-
- setProcessedQrCodes(prev => new Set(prev).add(latestQr));
- }
- }, [qrValues, isManualScanning, lineDetail?.id, processedQrCodes]);
- const lineAssumeEndTime = useMemo(() => {
- if (!lineDetail?.startTime || !lineDetail?.durationInMinutes) return null;
-
- // 解析 startTime(可能是数组或字符串)
- let start: dayjs.Dayjs;
- if (Array.isArray(lineDetail.startTime)) {
- const [year, month, day, hour = 0, minute = 0, second = 0] = lineDetail.startTime;
- start = dayjs(new Date(year, month - 1, day, hour, minute, second));
- } else if (typeof lineDetail.startTime === 'string') {
- // 检查是否是 "MM-DD HH:mm" 格式
- const mmddHhmmPattern = /^(\d{1,2})-(\d{1,2})\s+(\d{1,2}):(\d{1,2})$/;
- const match = lineDetail.startTime.match(mmddHhmmPattern);
-
- if (match) {
- const month = parseInt(match[1], 10);
- const day = parseInt(match[2], 10);
- const hour = parseInt(match[3], 10);
- const minute = parseInt(match[4], 10);
-
- // 使用当前年份,但如果跨年(startTime 是年末,当前是年初),使用上一年
- const now = dayjs();
- let year = now.year();
-
- if (month === 12 && day >= 20 && now.month() === 0 && now.date() <= 10) {
- year = now.year() - 1;
- }
-
- start = dayjs(new Date(year, month - 1, day, hour, minute, 0));
- } else {
- start = dayjs(lineDetail.startTime);
- }
- } else {
- start = dayjs(lineDetail.startTime as any);
- }
-
- if (!start.isValid()) return null;
-
- return start.add(lineDetail.durationInMinutes, 'minute');
- }, [lineDetail?.startTime, lineDetail?.durationInMinutes]);
- const lineStartTime = useMemo(() => {
- if (!lineDetail?.startTime) return null;
-
- let start: dayjs.Dayjs;
- if (Array.isArray(lineDetail.startTime)) {
- const [year, month, day, hour = 0, minute = 0, second = 0] = lineDetail.startTime;
- start = dayjs(new Date(year, month - 1, day, hour, minute, second));
- } else if (typeof lineDetail.startTime === 'string') {
- const mmddHhmmPattern = /^(\d{1,2})-(\d{1,2})\s+(\d{1,2}):(\d{1,2})$/;
- const match = lineDetail.startTime.match(mmddHhmmPattern);
-
- if (match) {
- const month = parseInt(match[1], 10);
- const day = parseInt(match[2], 10);
- const hour = parseInt(match[3], 10);
- const minute = parseInt(match[4], 10);
-
- const now = dayjs();
- let year = now.year();
-
- if (month === 12 && day >= 20 && now.month() === 0 && now.date() <= 10) {
- year = now.year() - 1;
- }
-
- start = dayjs(new Date(year, month - 1, day, hour, minute, 0));
- } else {
- start = dayjs(lineDetail.startTime);
- }
- } else {
- start = dayjs(lineDetail.startTime as any);
- }
-
- return start.isValid() ? start : null;
- }, [lineDetail?.startTime]);
- const handleOpenReasonModel = () => {
- setIsOpenReasonModel(true);
- setPauseReason("");
- };
- const handleCloseReasonModel = () => {
- setIsOpenReasonModel(false);
- setPauseReason("");
- };
- const handleSaveReason = async () => {
- if (!pauseReason.trim()) {
- alert(t("Please enter a reason for pausing"));
- return;
- }
- if (!lineDetail?.id) return;
-
- try {
- await saveProductProcessIssueTime({
- productProcessLineId: lineDetail.id,
- reason: pauseReason.trim()
- });
- setIsOpenReasonModel(false);
- setPauseReason("");
- fetchProductProcessLineDetail(lineDetail.id)
- .then((detail) => {
- setLineDetail(detail as any);
- })
- .catch(err => {
- console.error("Failed to load line detail", err);
- });
- } catch (error) {
- console.error("Error saving pause reason:", error);
- alert(t("Failed to pause. Please try again."));
- }
- };
-
- const handleResume = async () => {
- if (!lineDetail?.productProcessIssueId) {
- console.error("No productProcessIssueId found");
- return;
- }
-
- try {
- await saveProductProcessResumeTime(lineDetail.productProcessIssueId);
- console.log("✅ Resume API called successfully");
-
- if (lineDetail?.id) {
- fetchProductProcessLineDetail(lineDetail.id)
- .then((detail) => {
- console.log("✅ Line detail refreshed after resume:", detail);
- setLineDetail(detail as any);
- setFrozenRemainingTime(null);
- setLastPauseTime(null);
- })
- .catch(err => {
- console.error("❌ Failed to load line detail after resume", err);
- });
- }
- } catch (error) {
- console.error("❌ Error resuming:", error);
- alert(t("Failed to resume. Please try again."));
- }
- };
-
- return (
- <Box>
- <Box sx={{ mb: 2 }}>
- <Button variant="outlined" onClick={onBack}>
- {t("Back to List")}
- </Button>
- </Box>
- {processData && (
- <OverallTimeRemainingCard processData={processData} />
- )}
- {isCompleted ? (
- <Card sx={{ bgcolor: 'success.50', border: '2px solid', borderColor: 'success.main', mb: 3 }}>
- <CardContent>
- {lineDetail?.status === "Pass" ? (
- <Typography variant="h5" color="success.main" gutterBottom fontWeight="bold">
- {t("Passed Step")}: {lineDetail?.name} ({t("Seq")}: {lineDetail?.seqNo})
- </Typography>
- ) : (
- <Typography variant="h5" color="success.main" gutterBottom fontWeight="bold">
- {t("Completed Step")}: {lineDetail?.name} ({t("Seq")}: {lineDetail?.seqNo})
- </Typography>
- )}
- <Typography variant="h6" gutterBottom sx={{ mt: 2 }}>
- {t("Step Information")}
- </Typography>
- <Grid container spacing={2} sx={{ mb: 3 }}>
- <Grid item xs={12} md={6}>
- <Typography variant="body2" color="text.secondary" sx={{ fontSize: '1.25rem' }}>
- <strong>{t("Description")}:</strong> {lineDetail?.description || "-"}
- </Typography>
- </Grid>
- <Grid item xs={12} md={6}>
- <Typography variant="body2" color="text.secondary" sx={{ fontSize: '1.25rem' }}>
- <strong>{t("Operator")}:</strong> {lineDetail?.operatorName || "-"}
- </Typography>
- </Grid>
- <Grid item xs={12} md={6}>
- <Typography variant="body2" color="text.secondary" sx={{ fontSize: '1.25rem' }}>
- <strong>{t("Equipment")}:</strong> {equipmentName}
- </Typography>
- </Grid>
- <Grid item xs={12} md={6}>
- <Typography variant="body2" color="text.secondary" sx={{ fontSize: '1.25rem' }}>
- <strong>{t("Status")}:</strong> {t(lineDetail?.status || "-")}
- </Typography>
- </Grid>
- </Grid>
-
- <Typography variant="h6" gutterBottom sx={{ mt: 2 }}>
- {t("Production Output Data")}
- </Typography>
- <Table size="small" sx={{ mt: 2 }}>
- <TableHead>
- <TableRow>
- <TableCell width="25%"><strong>{t("Type")}</strong></TableCell>
- <TableCell width="25%"><strong>{t("Quantity")}</strong></TableCell>
- <TableCell width="25%"><strong>{t("Unit")}</strong></TableCell>
- <TableCell width="25%"><strong>{t("Description")}</strong></TableCell>
- </TableRow>
- </TableHead>
- <TableBody>
- <TableRow>
- <TableCell>
- <Typography fontWeight={500}>{t("Output from Process")}</Typography>
- </TableCell>
- <TableCell>
- <Typography>{lineDetail?.outputFromProcessQty || 0}</Typography>
- </TableCell>
- <TableCell>
- <Typography>{lineDetail?.outputFromProcessUom || "-"}</Typography>
- </TableCell>
- </TableRow>
-
- <TableRow sx={{ bgcolor: 'warning.50' }}>
- <TableCell>
- <Typography fontWeight={500} color="warning.dark">{t("Defect")}</Typography>
- </TableCell>
- <TableCell>
- <Typography>{lineDetail.defectQty}</Typography>
- </TableCell>
- <TableCell>
- <Typography>{lineDetail.defectUom || "-"}</Typography>
- </TableCell>
- <TableCell>
- <Typography>{lineDetail.defectDescription || "-"}</Typography>
- </TableCell>
- </TableRow>
- <TableRow sx={{ bgcolor: 'warning.50' }}>
- <TableCell>
- <Typography fontWeight={500} color="warning.dark">{t("Defect")}{t("(3)")}</Typography>
- </TableCell>
- <TableCell>
- <Typography>{lineDetail.defectQty3}</Typography>
- </TableCell>
- <TableCell>
- <Typography>{lineDetail.defectUom3 || "-"}</Typography>
- </TableCell>
- <TableCell>
- <Typography>{lineDetail.defectDescription3 || "-"}</Typography>
- </TableCell>
- </TableRow>
- <TableRow sx={{ bgcolor: 'warning.50' }}>
- <TableCell>
- <Typography fontWeight={500} color="warning.dark">{t("Defect")}{t("(2)")}</Typography>
- </TableCell>
- <TableCell>
- <Typography>{lineDetail.defectQty2}</Typography>
- </TableCell>
- <TableCell>
- <Typography>{lineDetail.defectUom2 || "-"}</Typography>
- </TableCell>
- <TableCell>
- <Typography>{lineDetail.defectDescription2 || "-"}</Typography>
- </TableCell>
- </TableRow>
- <TableRow sx={{ bgcolor: 'error.50' }}>
- <TableCell>
- <Typography fontWeight={500} color="error.dark">{t("Scrap")}</Typography>
- </TableCell>
- <TableCell>
- <Typography>{lineDetail.scrapQty}</Typography>
- </TableCell>
- <TableCell>
- <Typography>{lineDetail.scrapUom || "-"}</Typography>
- </TableCell>
- </TableRow>
- </TableBody>
- </Table>
- </CardContent>
- </Card>
- ) : (
- <>
- {!showOutputTable && (
- <Grid container spacing={2} sx={{ mb: 3 }}>
- <Grid item xs={12} >
- <Card sx={{ bgcolor: 'primary.50', border: '2px solid', borderColor: 'primary.main', height: '100%' }}>
- <CardContent>
- <Typography variant="h6" color="primary.main" gutterBottom>
- {t("Executing")}: {lineDetail?.name} ({t("Seq")}:{lineDetail?.seqNo})
- </Typography>
- <Typography variant="body2" color="text.secondary">
- {lineDetail?.description}
- </Typography>
- <Typography variant="body2" color="text.secondary">
- {t("Operator")}: {lineDetail?.operatorName || "-"}
- </Typography>
- <Typography variant="body2" color="text.secondary">
- {t("Equipment")}: {equipmentName}
- </Typography>
- {!isCompleted && remainingTime !== null && (
- <Box sx={{ mt: 2, mb: 2, p: 2, bgcolor: isOverTime ? 'error.50' : 'info.50', borderRadius: 1, border: '1px solid', borderColor: isOverTime ? 'error.main' : 'info.main' }}>
- <Typography variant="body2" color="text.secondary" gutterBottom>
- {t("Time Remaining")}
- </Typography>
- <Typography
- variant="h5"
- fontWeight="bold"
- color={isOverTime ? 'error.main' : 'info.main'}
- >
- {isOverTime ? `${t("Over Time")}: ${remainingTime}` : remainingTime}
- </Typography>
- {/* ✅ 添加:Process Start Time 和 Assume End Time */}
- {/* ✅ 添加:Process Start Time 和 Assume End Time */}
- {processData?.startTime && (
- <Box sx={{ mt: 2, pt: 2, borderTop: '1px solid', borderColor: 'divider' }}>
- <Typography variant="body2" color="text.secondary" gutterBottom>
- <strong>{t("Process Start Time")}:</strong> {dayjs(processData.startTime).format("MM-DD")} {dayjs(processData.startTime).format("HH:mm")}
- </Typography>
- </Box>
- )}
- {lineStartTime && (
- <Box sx={{ mt: 2, pt: 2, borderTop: '1px solid', borderColor: 'divider' }}>
- <Typography variant="body2" color="text.secondary" gutterBottom>
- <strong>{t("Step Start Time")}:</strong> {lineStartTime.format("MM-DD")} {lineStartTime.format("HH:mm")}
- </Typography>
- {lineAssumeEndTime && (
- <Typography variant="body2" color="text.secondary">
- <strong>{t("Assume End Time")}:</strong> {lineAssumeEndTime.format("MM-DD")} {lineAssumeEndTime.format("HH:mm")}
- </Typography>
- )}
- </Box>
- )}
- {lineDetail?.status === "Paused" && (
- <Typography variant="caption" color="warning.main" sx={{ mt: 0.5, display: 'block' }}>
- {t("Timer Paused")}
- </Typography>
- )}
- </Box>
- )}
- <Stack direction="row" spacing={2} justifyContent="center" sx={{ mt: 2 }}>
- { lineDetail?.status === 'InProgress'? (
- <Button
- variant="contained"
- color="warning"
- startIcon={<PauseIcon />}
- onClick={() => handleOpenReasonModel()}
- >
- {t("Pause")}
- </Button>
- ) : (
- <Button
- variant="contained"
- color="success"
- startIcon={<PlayArrowIcon />}
- onClick={handleResume}
- >
- {t("Continue")}
- </Button>
- )}
-
- <Button
- sx={{ mt: 2, alignSelf: "flex-end" }}
- variant="outlined"
- disabled={lineDetail?.status === 'Paused'}
- onClick={() => setShowOutputTable(true)}
- >
- {t("Order Complete")}
- </Button>
- </Stack>
- </CardContent>
- </Card>
- </Grid>
- </Grid>
- )}
- {/* ========== 产出输入表单 ========== */}
- {showOutputTable && (
- <Box>
- <Paper sx={{ p: 3, bgcolor: 'grey.50' }}>
- <Table size="small">
- <TableHead>
- <TableRow>
- <TableCell width="25%" align="center">{t("Type")}</TableCell>
- <TableCell width="25%" align="center">{t("Quantity")}</TableCell>
- <TableCell width="25%" align="center">{t("Unit")}</TableCell>
- <TableCell width="25%" align="center">{t(" ")}</TableCell>
- </TableRow>
- </TableHead>
- <TableBody>
- <TableRow>
- <TableCell>
- <Typography fontWeight={500}>{t("Output from Process")}</Typography>
- </TableCell>
- <TableCell>
- <TextField
- type="number"
- fullWidth
- size="small"
- value={outputData.outputFromProcessQty}
- onChange={(e) => setOutputData({
- ...outputData,
- outputFromProcessQty: parseInt(e.target.value) || 0
- })}
- />
- </TableCell>
- <TableCell>
- <TextField
- fullWidth
- size="small"
- value={outputData.outputFromProcessUom}
- onChange={(e) => setOutputData({
- ...outputData,
- outputFromProcessUom: e.target.value
- })}
- />
- </TableCell>
- <TableCell>
- <Typography fontSize={15} align="center"> <strong>{t("Description")}</strong></Typography>
- </TableCell>
- </TableRow>
-
- <TableRow sx={{ bgcolor: 'warning.50' }}>
- <TableCell>
- <Typography fontWeight={500} color="warning.dark">{t("Defect")}{t("(1)")}</Typography>
- </TableCell>
- <TableCell>
- <TextField
- type="number"
- fullWidth
- size="small"
- value={outputData.defectQty}
- onChange={(e) => setOutputData({
- ...outputData,
- defectQty: parseInt(e.target.value) || 0
- })}
- />
- </TableCell>
- <TableCell>
- <TextField
- fullWidth
- size="small"
- value={outputData.defectUom}
- onChange={(e) => setOutputData({
- ...outputData,
- defectUom: e.target.value
- })}
- />
- </TableCell>
- <TableCell>
- <TextField
- fullWidth
- size="small"
- onChange={(e) => setOutputData({
- ...outputData,
- defectDescription: e.target.value
- })}
- />
- </TableCell>
- </TableRow>
- <TableRow sx={{ bgcolor: 'warning.50' }}>
- <TableCell>
- <Typography fontWeight={500} color="warning.dark">{t("Defect")}{t("(2)")}</Typography>
- </TableCell>
- <TableCell>
- <TextField
- type="number"
- fullWidth
- size="small"
- value={outputData.defect2Qty}
- onChange={(e) => setOutputData({
- ...outputData,
- defect2Qty: parseInt(e.target.value) || 0
- })}
- />
- </TableCell>
- <TableCell>
- <TextField
- fullWidth
- size="small"
- value={outputData.defect2Uom}
- onChange={(e) => setOutputData({
- ...outputData,
- defect2Uom: e.target.value
- })}
- />
- </TableCell>
- <TableCell>
- <TextField
- fullWidth
- size="small"
- onChange={(e) => setOutputData({
- ...outputData,
- defectDescription2: e.target.value
- })}
- />
- </TableCell>
- </TableRow>
- <TableRow sx={{ bgcolor: 'warning.50' }}>
- <TableCell>
- <Typography fontWeight={500} color="warning.dark">{t("Defect")}{t("(3)")}</Typography>
- </TableCell>
- <TableCell>
- <TextField
- type="number"
- fullWidth
- size="small"
- value={outputData.defect3Qty}
- onChange={(e) => setOutputData({
- ...outputData,
- defect3Qty: parseInt(e.target.value) || 0
- })}
- />
- </TableCell>
- <TableCell>
- <TextField
- fullWidth
- size="small"
- value={outputData.defect3Uom}
- onChange={(e) => setOutputData({
- ...outputData,
- defect3Uom: e.target.value
- })}
- />
- </TableCell>
- <TableCell>
- <TextField
- fullWidth
- size="small"
- onChange={(e) => setOutputData({
- ...outputData,
- defectDescription3: e.target.value
- })}
- />
- </TableCell>
- </TableRow>
- <TableRow sx={{ bgcolor: 'error.50' }}>
- <TableCell>
- <Typography fontWeight={500} color="error.dark">{t("Scrap")}</Typography>
- </TableCell>
- <TableCell>
- <TextField
- type="number"
- fullWidth
- size="small"
- value={outputData.scrapQty}
- onChange={(e) => setOutputData({
- ...outputData,
- scrapQty: parseInt(e.target.value) || 0
- })}
- />
- </TableCell>
- <TableCell>
- <TextField
- fullWidth
- size="small"
- value={outputData.scrapUom}
- onChange={(e) => setOutputData({
- ...outputData,
- scrapUom: e.target.value
- })}
- />
- </TableCell>
- </TableRow>
- </TableBody>
- </Table>
-
- <Box sx={{ mt: 3, display: 'flex', gap: 2 }}>
- <Button
- variant="outlined"
- onClick={() => setShowOutputTable(false)}
- >
- {t("Cancel")}
- </Button>
- <Button
- variant="contained"
- startIcon={<CheckCircleIcon />}
- onClick={handleSubmitOutput}
- >
- {t("Complete Step")}
- </Button>
- </Box>
- </Paper>
- </Box>
- )}
-
- {/* ========== Bag Consumption Form ========== */}
- {((showOutputTable || isCompleted) && shouldShowBagForm && jobOrderId && lineId) && (
- <BagConsumptionForm
- jobOrderId={jobOrderId}
- lineId={lineId}
- bomDescription={processData?.bomDescription}
- isLastLine={shouldShowBagForm}
- onRefresh={handleRefreshLineDetail}
- />
- )}
- </>
- )}
- <Dialog
- open={isOpenReasonModel}
- onClose={handleCloseReasonModel}
- maxWidth="sm"
- fullWidth
- >
- <DialogTitle>{t("Pause Reason")}</DialogTitle>
- <DialogContent>
- <TextField
- autoFocus
- margin="dense"
- label={t("Reason")}
- fullWidth
- multiline
- rows={4}
- value={pauseReason}
- onChange={(e) => setPauseReason(e.target.value)}
- />
- </DialogContent>
- <DialogActions>
- <Button onClick={handleCloseReasonModel}>
- {t("Cancel")}
- </Button>
- <Button
- onClick={handleSaveReason}
- variant="contained"
- disabled={!pauseReason.trim()}
- >
- {t("Confirm")}
- </Button>
- </DialogActions>
- </Dialog>
- </Box>
- );
- };
-
- export default ProductionProcessStepExecution;
|