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