|
- "use client";
- import {
- Box,
- Button,
- Paper,
- Stack,
- Typography,
- TextField,
- Table,
- TableBody,
- TableCell,
- TableHead,
- TableRow,
- Card,
- CardContent,
- Grid,
- } 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} from "@/app/api/jo/actions";
- import { Operator, Machine } from "@/app/api/jo";
- import React, { useCallback, useEffect, useState } from "react";
- import { useQrCodeScannerContext } from "../QrCodeScannerProvider/QrCodeScannerProvider";
- import { fetchNameList, NameList } from "@/app/api/user/actions";
- interface ProductionProcessStepExecutionProps {
- lineId: number | null
- onBack: () => void
- //onClose: () => void
- // onOutputSubmitted: () => Promise<void>
- }
- const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionProps> = ({
- lineId,
- onBack,
- }) => {
- const { t } = useTranslation();
- const [lineDetail, setLineDetail] = useState<JobOrderProcessLineDetailResponse | null>(null);
- const isCompleted = lineDetail?.status === "Completed";
- 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: ""
- });
- 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 bothScanned = lineDetail?.operatorId && lineDetail?.equipmentId;
-
-
-
-
- useEffect(() => {
- if (!lineId) {
- setLineDetail(null);
- return;
- }
- fetchProductProcessLineDetail(lineId)
- .then((detail) => {
- setLineDetail(detail as any);
- // 初始化 outputData 从 lineDetail
- 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]);
- const handleSubmitOutput = async () => {
- if (!lineDetail?.id) return;
-
-
- try {
- // 直接使用 actions.ts 中定义的函数
- await updateProductProcessLineQty({
- productProcessLineId: lineDetail?.id || 0 as number,
- byproductName: outputData.byproductName,
- byproductQty: outputData.byproductQty,
- byproductUom: outputData.byproductUom,
- outputFromProcessQty: outputData.outputFromProcessQty,
- outputFromProcessUom: outputData.outputFromProcessUom,
- // outputFromProcessUom: outputData.outputFromProcessUom,
- defectQty: outputData.defectQty,
- defectUom: outputData.defectUom,
- scrapQty: outputData.scrapQty,
- scrapUom: outputData.scrapUom,
- });
-
- console.log(" Output data submitted successfully");
-
- fetchProductProcessLineDetail(lineDetail.id)
- .then((detail) => {
- setLineDetail(detail as any);
- // 初始化 outputData 从 lineDetail
- 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);
- });
- } catch (error) {
- console.error("Error submitting output:", error);
- alert("Failed to submit output data. Please try again.");
- }
- };
-
- // 处理 QR 码扫描效果
- 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));
- //processQrCode(latestQr);
- }
- }, [qrValues, isManualScanning, lineDetail?.id, processedQrCodes]);
-
- // 开始扫描
-
-
- const handlePause = () => {
- setIsPaused(true);
-
- };
-
- const handleContinue = () => {
- setIsPaused(false);
-
- };
-
- const handleStop = () => {
- setIsPaused(false);
-
- // TODO: 调用停止流程的 API
- };
-
- return (
- <Box>
- <Box sx={{ mb: 2 }}>
- <Button variant="outlined" onClick={onBack}>
- {t("Back to List")}
- </Button>
- </Box>
-
- {/* 如果已完成,显示合并的视图 */}
- {isCompleted ? (
- <Card sx={{ bgcolor: 'success.50', border: '2px solid', borderColor: 'success.main', mb: 3 }}>
- <CardContent>
- <Typography variant="h5" color="success.main" gutterBottom fontWeight="bold">
- {t("Completed Step")}: {lineDetail?.name} (Seq: {lineDetail?.seqNo})
- </Typography>
-
- {/*<Divider sx={{ my: 2 }} />*/}
-
- {/* 步骤信息部分 */}
- <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">
- <strong>{t("Description")}:</strong> {lineDetail?.description || "-"}
- </Typography>
- </Grid>
- <Grid item xs={12} md={6}>
- <Typography variant="body2" color="text.secondary">
- <strong>{t("Operator")}:</strong> {lineDetail?.operatorName || "-"}
- </Typography>
- </Grid>
- <Grid item xs={12} md={6}>
- <Typography variant="body2" color="text.secondary">
- <strong>{t("Equipment")}:</strong> {equipmentName}
- </Typography>
- </Grid>
- <Grid item xs={12} md={6}>
- <Typography variant="body2" color="text.secondary">
- <strong>{t("Status")}:</strong> {lineDetail?.status || "-"}
- </Typography>
- </Grid>
- </Grid>
-
- {/*<Divider sx={{ my: 2 }} />*/}
-
- {/* 产出数据部分 */}
- <Typography variant="h6" gutterBottom sx={{ mt: 2 }}>
- {t("Production Output Data")}
- </Typography>
- <Table size="small" sx={{ mt: 2 }}>
- <TableHead>
- <TableRow>
- <TableCell width="30%"><strong>{t("Type")}</strong></TableCell>
- <TableCell width="35%"><strong>{t("Quantity")}</strong></TableCell>
- <TableCell width="35%"><strong>{t("Unit")}</strong></TableCell>
- </TableRow>
- </TableHead>
- <TableBody>
- {/* Output from Process */}
- <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>
-
- {/* By-product */}
- {lineDetail?.byproductQty && lineDetail.byproductQty > 0 && (
- <TableRow>
- <TableCell>
- <Typography fontWeight={500}>{t("By-product")}</Typography>
- {lineDetail.byproductName && (
- <Typography variant="caption" color="text.secondary">
- ({lineDetail.byproductName})
- </Typography>
- )}
- </TableCell>
- <TableCell>
- <Typography>{lineDetail.byproductQty}</Typography>
- </TableCell>
- <TableCell>
- <Typography>{lineDetail.byproductUom || "-"}</Typography>
- </TableCell>
- </TableRow>
- )}
-
- {/* Defect */}
- {lineDetail?.defectQty && lineDetail.defectQty > 0 && (
- <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>
- </TableRow>
- )}
-
- {/* Scrap */}
- {lineDetail?.scrapQty && lineDetail.scrapQty > 0 && (
- <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>
- ) : (
- <>
- {/* 如果未完成,显示原来的两个部分 */}
- {/* 当前步骤信息 */}
- <Grid container spacing={2} sx={{ mb: 3 }}>
- <Grid item xs={12} md={6}>
- <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} (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>
- <Stack direction="row" spacing={2} justifyContent="center" sx={{ mt: 2 }}>
- <Button
- variant="contained"
- color="error"
- startIcon={<StopIcon />}
- onClick={handleStop}
- >
- {t("Stop")}
- </Button>
- {!isPaused ? (
- <Button
- variant="contained"
- color="warning"
- startIcon={<PauseIcon />}
- onClick={handlePause}
- >
- {t("Pause")}
- </Button>
- ) : (
- <Button
- variant="contained"
- color="success"
- startIcon={<PlayArrowIcon />}
- onClick={handleContinue}
- >
- {t("Continue")}
- </Button>
- )}
- </Stack>
- </CardContent>
- </Card>
- </Grid>
- </Grid>
-
- {/* ========== 产出输入表单 ========== */}
- <Box>
- <Box sx={{ mb: 2, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
- <Typography variant="h6" fontWeight={600}>
- {t("Production Output Data Entry")}
- </Typography>
- <Button
- variant="outlined"
- onClick={() => setShowOutputTable(!showOutputTable)}
- >
- {showOutputTable ? t("Hide Table") : t("Show Table")}
- </Button>
- </Box>
-
- {showOutputTable && (
- <Paper sx={{ p: 3, bgcolor: 'grey.50' }}>
- <Table size="small">
- <TableHead>
- <TableRow>
- <TableCell width="30%">{t("Type")}</TableCell>
- <TableCell width="35%">{t("Quantity")}</TableCell>
- <TableCell width="35%">{t("Unit")}</TableCell>
- </TableRow>
- </TableHead>
- <TableBody>
- {/* start line output */}
- <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>
- </TableRow>
- {/* byproduct */}
- <TableRow>
- <TableCell>
- <Stack>
- <Typography fontWeight={500}>{t("By-product")}</Typography>
- </Stack>
- </TableCell>
- <TableCell>
- <TextField
- type="number"
- fullWidth
- size="small"
- value={outputData.byproductQty}
- onChange={(e) => setOutputData({
- ...outputData,
- byproductQty: parseInt(e.target.value) || 0
- })}
- />
- </TableCell>
- <TableCell>
- <TextField
- fullWidth
- size="small"
- value={outputData.byproductUom}
- onChange={(e) => setOutputData({
- ...outputData,
- byproductUom: e.target.value
- })}
- />
- </TableCell>
- </TableRow>
-
- {/* defect */}
- <TableRow sx={{ bgcolor: 'warning.50' }}>
- <TableCell>
- <Typography fontWeight={500} color="warning.dark">{t("Defect")}</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>
- </TableRow>
-
- {/* scrap */}
- <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>
-
- {/* submit button */}
- <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>
- </>
- )}
- </Box>
- );
- };
-
- export default ProductionProcessStepExecution;
|