| @@ -18,6 +18,7 @@ import { useTranslation } from "react-i18next"; | |||
| import { UpdateProductProcessLineQtyRequest } from "@/app/api/jo/actions"; | |||
| interface ProductionOutputFormProps { | |||
| showByproduct?: boolean; | |||
| outputData: (UpdateProductProcessLineQtyRequest & { | |||
| byproductName: string; | |||
| byproductQty: number; | |||
| @@ -34,13 +35,16 @@ interface ProductionOutputFormProps { | |||
| >; | |||
| onSubmit: () => Promise<void> | void; | |||
| onCancel: () => void; | |||
| submitting?: boolean; | |||
| } | |||
| const ProductionOutputForm: React.FC<ProductionOutputFormProps> = ({ | |||
| showByproduct = false, | |||
| outputData, | |||
| setOutputData, | |||
| onSubmit, | |||
| onCancel, | |||
| submitting = false, | |||
| }) => { | |||
| const { t } = useTranslation(); | |||
| @@ -88,10 +92,14 @@ const ProductionOutputForm: React.FC<ProductionOutputFormProps> = ({ | |||
| </TableCell> | |||
| </TableRow> | |||
| {showByproduct && ( | |||
| <TableRow> | |||
| <TableCell> | |||
| <Stack> | |||
| <Typography fontWeight={500}>{t("By-product")}</Typography> | |||
| <Typography variant="body2" color="text.secondary"> | |||
| {outputData.byproductName} | |||
| </Typography> | |||
| </Stack> | |||
| </TableCell> | |||
| <TableCell> | |||
| @@ -106,22 +114,14 @@ const ProductionOutputForm: React.FC<ProductionOutputFormProps> = ({ | |||
| byproductQty: parseInt(e.target.value) || 0, | |||
| }) | |||
| } | |||
| inputProps={{ min: 0 }} | |||
| /> | |||
| </TableCell> | |||
| <TableCell> | |||
| <TextField | |||
| fullWidth | |||
| size="small" | |||
| value={outputData.byproductUom} | |||
| onChange={(e) => | |||
| setOutputData({ | |||
| ...outputData, | |||
| byproductUom: e.target.value, | |||
| }) | |||
| } | |||
| /> | |||
| <Typography>{outputData.byproductUom || "-"}</Typography> | |||
| </TableCell> | |||
| </TableRow> | |||
| )} | |||
| <TableRow sx={{ bgcolor: "warning.50" }}> | |||
| <TableCell> | |||
| @@ -199,7 +199,7 @@ const ProductionOutputForm: React.FC<ProductionOutputFormProps> = ({ | |||
| <Button variant="outlined" onClick={onCancel}> | |||
| {t("Cancel")} | |||
| </Button> | |||
| <Button variant="contained" startIcon={<CheckCircleIcon />} onClick={onSubmit}> | |||
| <Button variant="contained" startIcon={<CheckCircleIcon />} onClick={onSubmit} disabled={submitting}> | |||
| {t("Complete Step")} | |||
| </Button> | |||
| </Box> | |||
| @@ -1,5 +1,5 @@ | |||
| "use client"; | |||
| import React, { useEffect, useState } from "react"; | |||
| import React, { useEffect, useRef, useState } from "react"; | |||
| import { | |||
| Box, | |||
| Button, | |||
| @@ -25,6 +25,8 @@ const ProductionOutputFormPage: React.FC<ProductionOutputFormPageProps> = ({ | |||
| }) => { | |||
| const { t } = useTranslation(); | |||
| const [lineDetail, setLineDetail] = useState<JobOrderProcessLineDetailResponse | null>(null); | |||
| const outputInFlightRef = useRef(false); | |||
| const [isSubmitting, setIsSubmitting] = useState(false); | |||
| const [outputData, setOutputData] = useState<UpdateProductProcessLineQtyRequest & { | |||
| byproductName: string; | |||
| byproductQty: number; | |||
| @@ -79,6 +81,9 @@ const ProductionOutputFormPage: React.FC<ProductionOutputFormPageProps> = ({ | |||
| const handleSubmitOutput = async () => { | |||
| if (!lineDetail?.id) return; | |||
| if (outputInFlightRef.current) return; | |||
| outputInFlightRef.current = true; | |||
| setIsSubmitting(true); | |||
| try { | |||
| await updateProductProcessLineQty({ | |||
| @@ -110,6 +115,9 @@ const ProductionOutputFormPage: React.FC<ProductionOutputFormPageProps> = ({ | |||
| } catch (error) { | |||
| console.error("Error submitting output:", error); | |||
| alert("Failed to submit output data. Please try again."); | |||
| } finally { | |||
| outputInFlightRef.current = false; | |||
| setIsSubmitting(false); | |||
| } | |||
| }; | |||
| @@ -133,10 +141,12 @@ const ProductionOutputFormPage: React.FC<ProductionOutputFormPageProps> = ({ | |||
| </Box> | |||
| <ProductionOutputForm | |||
| showByproduct={(lineDetail?.byproductId ?? 0) > 0} | |||
| outputData={outputData} | |||
| setOutputData={setOutputData} | |||
| onSubmit={handleSubmitOutput} | |||
| onCancel={onBack} | |||
| submitting={isSubmitting} | |||
| /> | |||
| </Box> | |||
| ); | |||
| @@ -42,7 +42,7 @@ import { | |||
| ProductProcessLineResponse, // ✅ 添加 | |||
| } from "@/app/api/jo/actions"; | |||
| import { Operator, Machine } from "@/app/api/jo"; | |||
| import React, { useCallback, useEffect, useState, useMemo } from "react"; // ✅ 添加 useMemo | |||
| import React, { useCallback, useEffect, useState, useMemo, useRef } from "react"; | |||
| import { useQrCodeScannerContext } from "../QrCodeScannerProvider/QrCodeScannerProvider"; | |||
| import { fetchNameList, NameList } from "@/app/api/user/actions"; | |||
| import BagConsumptionForm from "./BagConsumptionForm"; // ✅ 添加导入 | |||
| @@ -114,6 +114,9 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro | |||
| const [lastPauseTime, setLastPauseTime] = useState<Date | null>(null); | |||
| const[isOpenReasonModel, setIsOpenReasonModel] = useState(false); | |||
| const [pauseReason, setPauseReason] = useState(""); | |||
| const outputInFlightRef = useRef(false); | |||
| const [isSubmittingOutput, setIsSubmittingOutput] = useState(false); | |||
| const hasByproduct = (lineDetail?.byproductId ?? 0) > 0; | |||
| // ✅ 添加:判断是否显示 Bag 表单的条件 | |||
| const isPackagingProcess = useMemo(() => { | |||
| @@ -360,6 +363,9 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro | |||
| const handleSubmitOutput = async () => { | |||
| if (!lineDetail?.id) return; | |||
| if (outputInFlightRef.current) return; | |||
| outputInFlightRef.current = true; | |||
| setIsSubmittingOutput(true); | |||
| try { | |||
| await updateProductProcessLineQty({ | |||
| @@ -422,6 +428,9 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro | |||
| } catch (error) { | |||
| console.error("Error submitting output:", error); | |||
| alert("Failed to submit output data. Please try again."); | |||
| } finally { | |||
| outputInFlightRef.current = false; | |||
| setIsSubmittingOutput(false); | |||
| } | |||
| }; | |||
| @@ -856,6 +865,34 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro | |||
| </TableCell> | |||
| </TableRow> | |||
| {hasByproduct && ( | |||
| <TableRow> | |||
| <TableCell> | |||
| <Typography fontWeight={500}>{t("By-product")}</Typography> | |||
| <Typography variant="body2" color="text.secondary"> | |||
| {outputData.byproductName} | |||
| </Typography> | |||
| </TableCell> | |||
| <TableCell> | |||
| <TextField | |||
| type="number" | |||
| fullWidth | |||
| size="small" | |||
| value={outputData.byproductQty} | |||
| onChange={(e) => setOutputData({ | |||
| ...outputData, | |||
| byproductQty: parseInt(e.target.value) || 0 | |||
| })} | |||
| inputProps={{ min: 0 }} | |||
| /> | |||
| </TableCell> | |||
| <TableCell> | |||
| <Typography>{outputData.byproductUom || "-"}</Typography> | |||
| </TableCell> | |||
| <TableCell /> | |||
| </TableRow> | |||
| )} | |||
| <TableRow sx={{ bgcolor: 'warning.50' }}> | |||
| <TableCell> | |||
| <Typography fontWeight={500} color="warning.dark">{t("Defect")}{t("(1)")}</Typography> | |||
| @@ -1052,6 +1089,7 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro | |||
| variant="contained" | |||
| startIcon={<CheckCircleIcon />} | |||
| onClick={handleSubmitOutput} | |||
| disabled={isSubmittingOutput} | |||
| > | |||
| {t("Complete Step")} | |||
| </Button> | |||