FPSMS-frontend
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

ProductionProcessStepExecution.tsx 43 KiB

pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 7 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 8 mēnešiem
pirms 10 mēnešiem
pirms 8 mēnešiem
pirms 10 mēnešiem
pirms 8 mēnešiem
pirms 10 mēnešiem
pirms 8 mēnešiem
pirms 10 mēnešiem
pirms 8 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 8 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 8 mēnešiem
pirms 7 mēnešiem
pirms 7 mēnešiem
pirms 7 mēnešiem
pirms 8 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 8 mēnešiem
pirms 10 mēnešiem
pirms 8 mēnešiem
pirms 10 mēnešiem
pirms 9 mēnešiem
pirms 10 mēnešiem
pirms 7 mēnešiem
pirms 9 mēnešiem
pirms 9 mēnešiem
pirms 9 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 9 mēnešiem
pirms 10 mēnešiem
pirms 9 mēnešiem
pirms 9 mēnešiem
pirms 9 mēnešiem
pirms 9 mēnešiem
pirms 10 mēnešiem
pirms 9 mēnešiem
pirms 9 mēnešiem
pirms 9 mēnešiem
pirms 9 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 9 mēnešiem
pirms 8 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 8 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 8 mēnešiem
pirms 8 mēnešiem
pirms 10 mēnešiem
pirms 8 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 7 mēnešiem
pirms 7 mēnešiem
pirms 8 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 8 mēnešiem
pirms 10 mēnešiem
pirms 8 mēnešiem
pirms 8 mēnešiem
pirms 10 mēnešiem
pirms 8 mēnešiem
pirms 10 mēnešiem
pirms 9 mēnešiem
pirms 10 mēnešiem
pirms 9 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 7 mēnešiem
pirms 10 mēnešiem
pirms 1 mēnesi
pirms 10 mēnešiem
pirms 1 mēnesi
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 1 mēnesi
pirms 10 mēnešiem
pirms 7 mēnešiem
pirms 10 mēnešiem
pirms 1 mēnesi
pirms 10 mēnešiem
pirms 1 mēnesi
pirms 10 mēnešiem
pirms 1 mēnesi
pirms 10 mēnešiem
pirms 7 mēnešiem
pirms 10 mēnešiem
pirms 1 mēnesi
pirms 10 mēnešiem
pirms 1 mēnesi
pirms 10 mēnešiem
pirms 1 mēnesi
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 1 mēnesi
pirms 10 mēnešiem
pirms 1 mēnesi
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 9 mēnešiem
pirms 10 mēnešiem
pirms 8 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 8 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 7 mēnešiem
pirms 10 mēnešiem
pirms 7 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 7 mēnešiem
pirms 10 mēnešiem
pirms 7 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 7 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 7 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 7 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 7 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 7 mēnešiem
pirms 10 mēnešiem
pirms 7 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 8 mēnešiem
pirms 8 mēnešiem
pirms 8 mēnešiem
pirms 8 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
pirms 10 mēnešiem
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098
  1. "use client";
  2. import {
  3. Box,
  4. Button,
  5. Paper,
  6. Stack,
  7. Typography,
  8. TextField,
  9. Table,
  10. TableBody,
  11. TableCell,
  12. TableHead,
  13. TableRow,
  14. Dialog,
  15. DialogTitle,
  16. DialogContent,
  17. DialogActions,
  18. Card,
  19. CardContent,
  20. Grid,
  21. Select,
  22. MenuItem,
  23. } from "@mui/material";
  24. import { Alert } from "@mui/material";
  25. import QrCodeIcon from '@mui/icons-material/QrCode';
  26. import CheckCircleIcon from "@mui/icons-material/CheckCircle";
  27. import StopIcon from "@mui/icons-material/Stop";
  28. import PauseIcon from "@mui/icons-material/Pause";
  29. import PlayArrowIcon from "@mui/icons-material/PlayArrow";
  30. import { useTranslation } from "react-i18next";
  31. import {
  32. JobOrderProcessLineDetailResponse,
  33. updateProductProcessLineQty,
  34. updateProductProcessLineQrscan,
  35. fetchProductProcessLineDetail,
  36. UpdateProductProcessLineQtyRequest,
  37. saveProductProcessResumeTime,
  38. saveProductProcessIssueTime,
  39. ProductProcessWithLinesResponse, // ✅ 添加
  40. ProductProcessLineResponse, // ✅ 添加
  41. } from "@/app/api/jo/actions";
  42. import { Operator, Machine } from "@/app/api/jo";
  43. import React, { useCallback, useEffect, useState, useMemo } from "react"; // ✅ 添加 useMemo
  44. import { useQrCodeScannerContext } from "../QrCodeScannerProvider/QrCodeScannerProvider";
  45. import { fetchNameList, NameList } from "@/app/api/user/actions";
  46. import BagConsumptionForm from "./BagConsumptionForm"; // ✅ 添加导入
  47. import OverallTimeRemainingCard from "./OverallTimeRemainingCard"; // ✅ 添加导入
  48. import dayjs from "dayjs";
  49. interface ProductionProcessStepExecutionProps {
  50. lineId: number | null;
  51. onBack: () => void;
  52. processData?: ProductProcessWithLinesResponse | null; // ✅ 添加
  53. allLines?: ProductProcessLineResponse[]; // ✅ 添加
  54. jobOrderId?: number; // ✅ 添加
  55. }
  56. /** FP-MTMS Version Checklist | Functions Ref. No. 59 | v1.0.0 | 2026-08-10 */
  57. const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionProps> = ({
  58. lineId,
  59. onBack,
  60. processData, // ✅ 添加
  61. allLines, // ✅ 添加
  62. jobOrderId, // ✅ 添加
  63. }) => {
  64. const { t } = useTranslation( ["common","jo","productionProcess"]);
  65. const [lineDetail, setLineDetail] = useState<JobOrderProcessLineDetailResponse | null>(null);
  66. const lineStatusNorm = String(lineDetail?.status ?? "")
  67. .trim()
  68. .toLowerCase()
  69. .replace(/\s+/g, "");
  70. const isCompleted =
  71. lineStatusNorm === "completed" ||
  72. lineStatusNorm === "pass" ||
  73. lineStatusNorm === "autopass";
  74. const isPassStatus = lineStatusNorm === "pass";
  75. const isAutoPassStatus = lineStatusNorm === "autopass";
  76. const [outputData, setOutputData] = useState<UpdateProductProcessLineQtyRequest & {
  77. byproductName: string;
  78. byproductQty: number;
  79. byproductUom: string;
  80. }>({
  81. productProcessLineId: lineId ?? 0,
  82. outputFromProcessQty: 0,
  83. outputFromProcessUom: "",
  84. defectQty: 0,
  85. defectUom: "",
  86. scrapQty: 0,
  87. scrapUom: "",
  88. byproductName: "",
  89. byproductQty: 0,
  90. byproductUom: "",
  91. defect2Qty: 0,
  92. defect2Uom: "",
  93. defect3Qty: 0,
  94. defect3Uom: "",
  95. defectDescription: "",
  96. defectDescription2: "",
  97. defectDescription3: ""
  98. });
  99. const [isManualScanning, setIsManualScanning] = useState(false);
  100. const [processedQrCodes, setProcessedQrCodes] = useState<Set<string>>(new Set());
  101. const [scannedOperators, setScannedOperators] = useState<Operator[]>([]);
  102. const [scannedMachines, setScannedMachines] = useState<Machine[]>([]);
  103. const [isPaused, setIsPaused] = useState(false);
  104. const [showOutputTable, setShowOutputTable] = useState(false);
  105. const { values: qrValues, startScan, stopScan, resetScan } = useQrCodeScannerContext();
  106. const equipmentName = (lineDetail as any)?.equipment || lineDetail?.equipmentType || "-";
  107. const [remainingTime, setRemainingTime] = useState<string | null>(null);
  108. const [isOverTime, setIsOverTime] = useState(false);
  109. const [frozenRemainingTime, setFrozenRemainingTime] = useState<string | null>(null);
  110. const [lastPauseTime, setLastPauseTime] = useState<Date | null>(null);
  111. const[isOpenReasonModel, setIsOpenReasonModel] = useState(false);
  112. const [pauseReason, setPauseReason] = useState("");
  113. // ✅ 添加:判断是否显示 Bag 表单的条件
  114. const isPackagingProcess = useMemo(() => {
  115. if (!lineDetail) return false;
  116. return lineDetail.name === "包裝";
  117. }, [lineDetail])
  118. const uomList = [
  119. "千克(KG)","克(G)","磅(LB)","安士(OZ)","斤(CATTY)","公升(L)","毫升(ML)"
  120. ];
  121. // ✅ 添加:刷新 line detail 的函数
  122. const handleRefreshLineDetail = useCallback(async () => {
  123. if (lineId) {
  124. try {
  125. const detail = await fetchProductProcessLineDetail(lineId);
  126. setLineDetail(detail as any);
  127. } catch (error) {
  128. console.error("Failed to refresh line detail", error);
  129. }
  130. }
  131. }, [lineId]);
  132. useEffect(() => {
  133. if (!lineId) {
  134. setLineDetail(null);
  135. return;
  136. }
  137. fetchProductProcessLineDetail(lineId)
  138. .then((detail) => {
  139. setLineDetail(detail as any);
  140. console.log("📋 Line Detail loaded:", {
  141. id: detail.id,
  142. status: detail.status,
  143. durationInMinutes: detail.durationInMinutes,
  144. startTime: detail.startTime,
  145. startTimeType: typeof detail.startTime,
  146. hasDuration: !!detail.durationInMinutes,
  147. hasStartTime: !!detail.startTime,
  148. });
  149. setOutputData(prev => ({
  150. ...prev,
  151. productProcessLineId: detail.id,
  152. outputFromProcessQty: (detail as any).outputFromProcessQty || 0,
  153. outputFromProcessUom: (detail as any).outputFromProcessUom || "",
  154. defectQty: detail.defectQty || 0,
  155. defectUom: detail.defectUom || "",
  156. scrapQty: detail.scrapQty || 0,
  157. scrapUom: detail.scrapUom || "",
  158. byproductName: detail.byproductName || "",
  159. byproductQty: detail.byproductQty || 0,
  160. byproductUom: detail.byproductUom || ""
  161. }));
  162. })
  163. .catch(err => {
  164. console.error("Failed to load line detail", err);
  165. setLineDetail(null);
  166. });
  167. }, [lineId]);
  168. useEffect(() => {
  169. // Don't show time remaining if completed / pass / autoPass
  170. const statusNorm = String(lineDetail?.status ?? "")
  171. .trim()
  172. .toLowerCase()
  173. .replace(/\s+/g, "");
  174. if (
  175. statusNorm === "completed" ||
  176. statusNorm === "pass" ||
  177. statusNorm === "autopass"
  178. ) {
  179. console.log("Line is completed");
  180. setRemainingTime(null);
  181. setIsOverTime(false);
  182. return;
  183. }
  184. console.log("🔍 Time Remaining Debug:", {
  185. lineId: lineDetail?.id,
  186. equipmentId: lineDetail?.equipmentId,
  187. equipmentType: lineDetail?.equipmentType,
  188. durationInMinutes: lineDetail?.durationInMinutes,
  189. startTime: lineDetail?.startTime,
  190. startTimeType: typeof lineDetail?.startTime,
  191. isStartTimeArray: Array.isArray(lineDetail?.startTime),
  192. status: lineDetail?.status,
  193. hasDuration: !!lineDetail?.durationInMinutes,
  194. hasStartTime: !!lineDetail?.startTime,
  195. });
  196. if (!lineDetail?.durationInMinutes || !lineDetail?.startTime) {
  197. console.log(" Line duration or start time is not valid", {
  198. durationInMinutes: lineDetail?.durationInMinutes,
  199. startTime: lineDetail?.startTime,
  200. equipmentId: lineDetail?.equipmentId,
  201. equipmentType: lineDetail?.equipmentType,
  202. });
  203. setRemainingTime(null);
  204. setIsOverTime(false);
  205. return;
  206. }
  207. let start: Date;
  208. if (Array.isArray(lineDetail.startTime)) {
  209. console.log("Line start time is an array:", lineDetail.startTime);
  210. const [year, month, day, hour = 0, minute = 0, second = 0] = lineDetail.startTime;
  211. start = new Date(year, month - 1, day, hour, minute, second);
  212. } else {
  213. start = new Date(lineDetail.startTime);
  214. console.log("Line start time is a string:", lineDetail.startTime);
  215. }
  216. if (isNaN(start.getTime())) {
  217. console.error("Invalid startTime:", lineDetail.startTime);
  218. setRemainingTime(null);
  219. setIsOverTime(false);
  220. return;
  221. }
  222. const durationMs = lineDetail.durationInMinutes * 60_000;
  223. const isPaused = lineDetail.status === "Paused" || lineDetail.productProcessIssueStatus === "Paused";
  224. const parseStopTime = (stopTime: string | number[] | undefined): Date | null => {
  225. if (!stopTime) return null;
  226. if (Array.isArray(stopTime)) {
  227. const [year, month, day, hour = 0, minute = 0, second = 0] = stopTime;
  228. return new Date(year, month - 1, day, hour, minute, second);
  229. } else {
  230. return new Date(stopTime);
  231. }
  232. };
  233. const update = () => {
  234. if (isPaused) {
  235. if (!frozenRemainingTime) {
  236. const pauseTime = lineDetail.stopTime
  237. ? parseStopTime(lineDetail.stopTime)
  238. : null;
  239. const pauseTimeToUse = pauseTime && !isNaN(pauseTime.getTime())
  240. ? pauseTime
  241. : new Date();
  242. const totalPausedTimeMs = (lineDetail as any).totalPausedTimeMs || 0;
  243. console.log("⏸️ Paused - calculating frozen time:", {
  244. stopTime: lineDetail.stopTime,
  245. pauseTime: pauseTimeToUse,
  246. startTime: start,
  247. totalPausedTimeMs: totalPausedTimeMs,
  248. });
  249. const elapsed = pauseTimeToUse.getTime() - start.getTime() - totalPausedTimeMs;
  250. const remaining = durationMs - elapsed;
  251. if (remaining <= 0) {
  252. const overTime = Math.abs(remaining);
  253. const minutes = Math.floor(overTime / 60000).toString().padStart(2, "0");
  254. const seconds = Math.floor((overTime % 60000) / 1000).toString().padStart(2, "0");
  255. const frozenValue = `-${minutes}:${seconds}`;
  256. setFrozenRemainingTime(frozenValue);
  257. setRemainingTime(frozenValue);
  258. setIsOverTime(true);
  259. console.log("⏸️ Frozen time (overtime):", frozenValue);
  260. } else {
  261. const minutes = Math.floor(remaining / 60000).toString().padStart(2, "0");
  262. const seconds = Math.floor((remaining % 60000) / 1000).toString().padStart(2, "0");
  263. const frozenValue = `${minutes}:${seconds}`;
  264. setFrozenRemainingTime(frozenValue);
  265. setRemainingTime(frozenValue);
  266. setIsOverTime(false);
  267. console.log("⏸️ Frozen time:", frozenValue);
  268. }
  269. } else {
  270. setRemainingTime(frozenRemainingTime);
  271. console.log("⏸️ Using frozen time:", frozenRemainingTime);
  272. }
  273. return;
  274. }
  275. if (frozenRemainingTime && !isPaused) {
  276. console.log("▶️ Resumed - clearing frozen time");
  277. setFrozenRemainingTime(null);
  278. setLastPauseTime(null);
  279. }
  280. const totalPausedTimeMs = (lineDetail as any).totalPausedTimeMs || 0;
  281. const now = new Date();
  282. const elapsed = now.getTime() - start.getTime() - totalPausedTimeMs;
  283. const remaining = durationMs - elapsed;
  284. console.log("⏱️ Time calculation:", {
  285. now: now,
  286. start: start,
  287. totalPausedTimeMs: totalPausedTimeMs,
  288. elapsed: elapsed,
  289. remaining: remaining,
  290. durationMs: durationMs,
  291. });
  292. if (remaining <= 0) {
  293. const overTime = Math.abs(remaining);
  294. const minutes = Math.floor(overTime / 60000).toString().padStart(2, "0");
  295. const seconds = Math.floor((overTime % 60000) / 1000).toString().padStart(2, "0");
  296. setRemainingTime(`-${minutes}:${seconds}`);
  297. setIsOverTime(true);
  298. } else {
  299. const minutes = Math.floor(remaining / 60000).toString().padStart(2, "0");
  300. const seconds = Math.floor((remaining % 60000) / 1000).toString().padStart(2, "0");
  301. setRemainingTime(`${minutes}:${seconds}`);
  302. setIsOverTime(false);
  303. }
  304. };
  305. update();
  306. if (!isPaused) {
  307. const timer = setInterval(update, 1000);
  308. return () => clearInterval(timer);
  309. }
  310. }, [lineDetail?.durationInMinutes, lineDetail?.startTime, lineDetail?.status, lineDetail?.productProcessIssueStatus, lineDetail?.stopTime, frozenRemainingTime]);
  311. useEffect(() => {
  312. const wasPaused = lineDetail?.status === "Paused" || lineDetail?.productProcessIssueStatus === "Paused";
  313. const isNowInProgress = lineDetail?.status === "InProgress";
  314. if (wasPaused && isNowInProgress && frozenRemainingTime) {
  315. setFrozenRemainingTime(null);
  316. }
  317. }, [lineDetail?.status, lineDetail?.productProcessIssueStatus]);
  318. const handleSubmitOutput = async () => {
  319. if (!lineDetail?.id) return;
  320. try {
  321. await updateProductProcessLineQty({
  322. productProcessLineId: lineDetail?.id || 0 as number,
  323. byproductName: outputData.byproductName,
  324. byproductQty: outputData.byproductQty,
  325. byproductUom: outputData.byproductUom,
  326. outputFromProcessQty: outputData.outputFromProcessQty,
  327. outputFromProcessUom: outputData.outputFromProcessUom,
  328. defectQty: outputData.defectQty,
  329. defectUom: outputData.defectUom,
  330. defect2Qty: outputData.defect2Qty,
  331. defect2Uom: outputData.defect2Uom,
  332. defect3Qty: outputData.defect3Qty,
  333. defect3Uom: outputData.defect3Uom,
  334. defectDescription: outputData.defectDescription,
  335. defectDescription2: outputData.defectDescription2,
  336. defectDescription3: outputData.defectDescription3,
  337. scrapQty: outputData.scrapQty,
  338. scrapUom: outputData.scrapUom,
  339. });
  340. console.log(" Output data submitted successfully");
  341. fetchProductProcessLineDetail(lineDetail.id)
  342. .then((detail) => {
  343. console.log("Line Detail loaded:", {
  344. id: detail.id,
  345. status: detail.status,
  346. startTime: detail.startTime,
  347. durationInMinutes: detail.durationInMinutes,
  348. productProcessIssueStatus: detail.productProcessIssueStatus
  349. });
  350. setLineDetail(detail as any);
  351. setOutputData(prev => ({
  352. ...prev,
  353. productProcessLineId: detail.id,
  354. outputFromProcessQty: (detail as any).outputFromProcessQty || 0,
  355. outputFromProcessUom: (detail as any).outputFromProcessUom || "",
  356. defectQty: detail.defectQty || 0,
  357. defectUom: detail.defectUom || "",
  358. defectDescription: detail.defectDescription || "",
  359. defectDescription2: detail.defectDescription2 || "",
  360. defectDescription3: detail.defectDescription3 || "",
  361. defectQty2: detail.defectQty2 || 0,
  362. defectUom2: detail.defectUom2 || "",
  363. defectQty3: detail.defectQty3 || 0,
  364. defectUom3: detail.defectUom3 || "",
  365. scrapQty: detail.scrapQty || 0,
  366. scrapUom: detail.scrapUom || "",
  367. byproductName: detail.byproductName || "",
  368. byproductQty: detail.byproductQty || 0,
  369. byproductUom: detail.byproductUom || ""
  370. }));
  371. })
  372. .catch(err => {
  373. console.error("Failed to load line detail", err);
  374. setLineDetail(null);
  375. });
  376. } catch (error) {
  377. console.error("Error submitting output:", error);
  378. alert("Failed to submit output data. Please try again.");
  379. }
  380. };
  381. useEffect(() => {
  382. if (isManualScanning && qrValues.length > 0 && lineDetail?.id) {
  383. const latestQr = qrValues[qrValues.length - 1];
  384. if (processedQrCodes.has(latestQr)) {
  385. return;
  386. }
  387. setProcessedQrCodes(prev => new Set(prev).add(latestQr));
  388. }
  389. }, [qrValues, isManualScanning, lineDetail?.id, processedQrCodes]);
  390. const lineAssumeEndTime = useMemo(() => {
  391. if (!lineDetail?.startTime || !lineDetail?.durationInMinutes) return null;
  392. // 解析 startTime(可能是数组或字符串)
  393. let start: dayjs.Dayjs;
  394. if (Array.isArray(lineDetail.startTime)) {
  395. const [year, month, day, hour = 0, minute = 0, second = 0] = lineDetail.startTime;
  396. start = dayjs(new Date(year, month - 1, day, hour, minute, second));
  397. } else if (typeof lineDetail.startTime === 'string') {
  398. // 检查是否是 "MM-DD HH:mm" 格式
  399. const mmddHhmmPattern = /^(\d{1,2})-(\d{1,2})\s+(\d{1,2}):(\d{1,2})$/;
  400. const match = lineDetail.startTime.match(mmddHhmmPattern);
  401. if (match) {
  402. const month = parseInt(match[1], 10);
  403. const day = parseInt(match[2], 10);
  404. const hour = parseInt(match[3], 10);
  405. const minute = parseInt(match[4], 10);
  406. // 使用当前年份,但如果跨年(startTime 是年末,当前是年初),使用上一年
  407. const now = dayjs();
  408. let year = now.year();
  409. if (month === 12 && day >= 20 && now.month() === 0 && now.date() <= 10) {
  410. year = now.year() - 1;
  411. }
  412. start = dayjs(new Date(year, month - 1, day, hour, minute, 0));
  413. } else {
  414. start = dayjs(lineDetail.startTime);
  415. }
  416. } else {
  417. start = dayjs(lineDetail.startTime as any);
  418. }
  419. if (!start.isValid()) return null;
  420. return start.add(lineDetail.durationInMinutes, 'minute');
  421. }, [lineDetail?.startTime, lineDetail?.durationInMinutes]);
  422. const lineStartTime = useMemo(() => {
  423. if (!lineDetail?.startTime) return null;
  424. let start: dayjs.Dayjs;
  425. if (Array.isArray(lineDetail.startTime)) {
  426. const [year, month, day, hour = 0, minute = 0, second = 0] = lineDetail.startTime;
  427. start = dayjs(new Date(year, month - 1, day, hour, minute, second));
  428. } else if (typeof lineDetail.startTime === 'string') {
  429. const mmddHhmmPattern = /^(\d{1,2})-(\d{1,2})\s+(\d{1,2}):(\d{1,2})$/;
  430. const match = lineDetail.startTime.match(mmddHhmmPattern);
  431. if (match) {
  432. const month = parseInt(match[1], 10);
  433. const day = parseInt(match[2], 10);
  434. const hour = parseInt(match[3], 10);
  435. const minute = parseInt(match[4], 10);
  436. const now = dayjs();
  437. let year = now.year();
  438. if (month === 12 && day >= 20 && now.month() === 0 && now.date() <= 10) {
  439. year = now.year() - 1;
  440. }
  441. start = dayjs(new Date(year, month - 1, day, hour, minute, 0));
  442. } else {
  443. start = dayjs(lineDetail.startTime);
  444. }
  445. } else {
  446. start = dayjs(lineDetail.startTime as any);
  447. }
  448. return start.isValid() ? start : null;
  449. }, [lineDetail?.startTime]);
  450. const handleOpenReasonModel = () => {
  451. setIsOpenReasonModel(true);
  452. setPauseReason("");
  453. };
  454. const handleCloseReasonModel = () => {
  455. setIsOpenReasonModel(false);
  456. setPauseReason("");
  457. };
  458. const handleSaveReason = async () => {
  459. if (!pauseReason.trim()) {
  460. alert(t("Please enter a reason for pausing"));
  461. return;
  462. }
  463. if (!lineDetail?.id) return;
  464. try {
  465. await saveProductProcessIssueTime({
  466. productProcessLineId: lineDetail.id,
  467. reason: pauseReason.trim()
  468. });
  469. setIsOpenReasonModel(false);
  470. setPauseReason("");
  471. fetchProductProcessLineDetail(lineDetail.id)
  472. .then((detail) => {
  473. setLineDetail(detail as any);
  474. })
  475. .catch(err => {
  476. console.error("Failed to load line detail", err);
  477. });
  478. } catch (error) {
  479. console.error("Error saving pause reason:", error);
  480. alert(t("Failed to pause. Please try again."));
  481. }
  482. };
  483. const handleResume = async () => {
  484. if (!lineDetail?.productProcessIssueId) {
  485. console.error("No productProcessIssueId found");
  486. return;
  487. }
  488. try {
  489. await saveProductProcessResumeTime(lineDetail.productProcessIssueId);
  490. console.log("✅ Resume API called successfully");
  491. if (lineDetail?.id) {
  492. fetchProductProcessLineDetail(lineDetail.id)
  493. .then((detail) => {
  494. console.log("✅ Line detail refreshed after resume:", detail);
  495. setLineDetail(detail as any);
  496. setFrozenRemainingTime(null);
  497. setLastPauseTime(null);
  498. })
  499. .catch(err => {
  500. console.error(" Failed to load line detail after resume", err);
  501. });
  502. }
  503. } catch (error) {
  504. console.error(" Error resuming:", error);
  505. alert(t("Failed to resume. Please try again."));
  506. }
  507. };
  508. return (
  509. <Box>
  510. <Box sx={{ mb: 2 }}>
  511. <Button variant="outlined" onClick={onBack}>
  512. {t("Back to List")}
  513. </Button>
  514. </Box>
  515. {processData && (
  516. <OverallTimeRemainingCard processData={processData} />
  517. )}
  518. {isCompleted ? (
  519. <Card sx={{ bgcolor: 'success.50', border: '2px solid', borderColor: 'success.main', mb: 3 }}>
  520. <CardContent>
  521. {isAutoPassStatus ? (
  522. <Typography variant="h5" color="success.main" gutterBottom fontWeight="bold">
  523. {t("Auto Pass")}: {lineDetail?.name} ({t("Seq")}: {lineDetail?.seqNo})
  524. </Typography>
  525. ) : isPassStatus ? (
  526. <Typography variant="h5" color="success.main" gutterBottom fontWeight="bold">
  527. {t("Just Pass")}: {lineDetail?.name} ({t("Seq")}: {lineDetail?.seqNo})
  528. </Typography>
  529. ) : (
  530. <Typography variant="h5" color="success.main" gutterBottom fontWeight="bold">
  531. {t("Completed Step")}: {lineDetail?.name} ({t("Seq")}: {lineDetail?.seqNo})
  532. </Typography>
  533. )}
  534. <Typography variant="h6" gutterBottom sx={{ mt: 2 }}>
  535. {t("Step Information")}
  536. </Typography>
  537. <Grid container spacing={2} sx={{ mb: 3 }}>
  538. <Grid item xs={12} md={6}>
  539. <Typography variant="body2" color="text.secondary" sx={{ fontSize: '1.25rem' }}>
  540. <strong>{t("Description")}:</strong> {lineDetail?.description || "-"}
  541. </Typography>
  542. </Grid>
  543. <Grid item xs={12} md={6}>
  544. <Typography variant="body2" color="text.secondary" sx={{ fontSize: '1.25rem' }}>
  545. <strong>{t("Operator")}:</strong> {lineDetail?.operatorName || "-"}
  546. </Typography>
  547. </Grid>
  548. <Grid item xs={12} md={6}>
  549. <Typography variant="body2" color="text.secondary" sx={{ fontSize: '1.25rem' }}>
  550. <strong>{t("Equipment")}:</strong> {equipmentName}
  551. </Typography>
  552. </Grid>
  553. <Grid item xs={12} md={6}>
  554. <Typography variant="body2" color="text.secondary" sx={{ fontSize: '1.25rem' }}>
  555. <strong>{t("Status")}:</strong> {t(lineDetail?.status || "-")}
  556. </Typography>
  557. </Grid>
  558. </Grid>
  559. <Typography variant="h6" gutterBottom sx={{ mt: 2 }}>
  560. {t("Production Output Data")}
  561. </Typography>
  562. <Table size="small" sx={{ mt: 2 }}>
  563. <TableHead>
  564. <TableRow>
  565. <TableCell width="25%"><strong>{t("Type")}</strong></TableCell>
  566. <TableCell width="25%"><strong>{t("Quantity")}</strong></TableCell>
  567. <TableCell width="25%"><strong>{t("Unit")}</strong></TableCell>
  568. <TableCell width="25%"><strong>{t("Description")}</strong></TableCell>
  569. </TableRow>
  570. </TableHead>
  571. <TableBody>
  572. <TableRow>
  573. <TableCell>
  574. <Typography fontWeight={500}>{t("Output from Process")}</Typography>
  575. </TableCell>
  576. <TableCell>
  577. <Typography>{lineDetail?.outputFromProcessQty || 0}</Typography>
  578. </TableCell>
  579. <TableCell>
  580. <Typography>{lineDetail?.outputFromProcessUom || "-"}</Typography>
  581. </TableCell>
  582. </TableRow>
  583. <TableRow sx={{ bgcolor: 'warning.50' }}>
  584. <TableCell>
  585. <Typography fontWeight={500} color="warning.dark">{t("Defect")}{t("(1)")}</Typography>
  586. </TableCell>
  587. <TableCell>
  588. <Typography>{lineDetail?.defectQty}</Typography>
  589. </TableCell>
  590. <TableCell>
  591. <Typography>{lineDetail?.defectUom || "-"}</Typography>
  592. </TableCell>
  593. <TableCell>
  594. <Typography>{lineDetail?.defectDescription || "-"}</Typography>
  595. </TableCell>
  596. </TableRow>
  597. <TableRow sx={{ bgcolor: 'warning.50' }}>
  598. <TableCell>
  599. <Typography fontWeight={500} color="warning.dark">{t("Defect")}{t("(2)")}</Typography>
  600. </TableCell>
  601. <TableCell>
  602. <Typography>{lineDetail?.defectQty2}</Typography>
  603. </TableCell>
  604. <TableCell>
  605. <Typography>{lineDetail?.defectUom2 || "-"}</Typography>
  606. </TableCell>
  607. <TableCell>
  608. <Typography>{lineDetail?.defectDescription2 || "-"}</Typography>
  609. </TableCell>
  610. </TableRow>
  611. <TableRow sx={{ bgcolor: 'warning.50' }}>
  612. <TableCell>
  613. <Typography fontWeight={500} color="warning.dark">{t("Defect")}{t("(3)")}</Typography>
  614. </TableCell>
  615. <TableCell>
  616. <Typography>{lineDetail?.defectQty3}</Typography>
  617. </TableCell>
  618. <TableCell>
  619. <Typography>{lineDetail?.defectUom3 || "-"}</Typography>
  620. </TableCell>
  621. <TableCell>
  622. <Typography>{lineDetail?.defectDescription3 || "-"}</Typography>
  623. </TableCell>
  624. </TableRow>
  625. <TableRow sx={{ bgcolor: 'error.50' }}>
  626. <TableCell>
  627. <Typography fontWeight={500} color="error.dark">{t("Scrap")}</Typography>
  628. </TableCell>
  629. <TableCell>
  630. <Typography>{lineDetail?.scrapQty}</Typography>
  631. </TableCell>
  632. <TableCell>
  633. <Typography>{lineDetail?.scrapUom || "-"}</Typography>
  634. </TableCell>
  635. </TableRow>
  636. </TableBody>
  637. </Table>
  638. </CardContent>
  639. </Card>
  640. ) : (
  641. <>
  642. {!showOutputTable && (
  643. <Grid container spacing={2} sx={{ mb: 3 }}>
  644. <Grid item xs={12} >
  645. <Card sx={{ bgcolor: 'primary.50', border: '2px solid', borderColor: 'primary.main', height: '100%' }}>
  646. <CardContent>
  647. <Typography variant="h6" color="primary.main" gutterBottom>
  648. {t("Executing")}: {lineDetail?.name} ({t("Seq")}:{lineDetail?.seqNo})
  649. </Typography>
  650. <Typography variant="body2" color="text.secondary">
  651. {lineDetail?.description}
  652. </Typography>
  653. <Typography variant="body2" color="text.secondary">
  654. {t("Operator")}: {lineDetail?.operatorName || "-"}
  655. </Typography>
  656. <Typography variant="body2" color="text.secondary">
  657. {t("Equipment")}: {equipmentName}
  658. </Typography>
  659. {!isCompleted && remainingTime !== null && (
  660. <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' }}>
  661. <Typography variant="body2" color="text.secondary" gutterBottom>
  662. {t("Time Remaining")}
  663. </Typography>
  664. <Typography
  665. variant="h5"
  666. fontWeight="bold"
  667. color={isOverTime ? 'error.main' : 'info.main'}
  668. >
  669. {isOverTime ? `${t("Over Time")}: ${remainingTime}` : remainingTime}
  670. </Typography>
  671. {/* ✅ 添加:Process Start Time 和 Assume End Time */}
  672. {/* ✅ 添加:Process Start Time 和 Assume End Time */}
  673. {processData?.startTime && (
  674. <Box sx={{ mt: 2, pt: 2, borderTop: '1px solid', borderColor: 'divider' }}>
  675. <Typography variant="body2" color="text.secondary" gutterBottom>
  676. <strong>{t("Process Start Time")}:</strong> {dayjs(processData.startTime).format("MM-DD")} {dayjs(processData.startTime).format("HH:mm")}
  677. </Typography>
  678. </Box>
  679. )}
  680. {lineStartTime && (
  681. <Box sx={{ mt: 2, pt: 2, borderTop: '1px solid', borderColor: 'divider' }}>
  682. <Typography variant="body2" color="text.secondary" gutterBottom>
  683. <strong>{t("Step Start Time")}:</strong> {lineStartTime.format("MM-DD")} {lineStartTime.format("HH:mm")}
  684. </Typography>
  685. {lineAssumeEndTime && (
  686. <Typography variant="body2" color="text.secondary">
  687. <strong>{t("Assume End Time")}:</strong> {lineAssumeEndTime.format("MM-DD")} {lineAssumeEndTime.format("HH:mm")}
  688. </Typography>
  689. )}
  690. </Box>
  691. )}
  692. {lineDetail?.status === "Paused" && (
  693. <Typography variant="caption" color="warning.main" sx={{ mt: 0.5, display: 'block' }}>
  694. {t("Timer Paused")}
  695. </Typography>
  696. )}
  697. </Box>
  698. )}
  699. <Stack direction="row" spacing={2} justifyContent="center" sx={{ mt: 2 }}>
  700. { lineDetail?.status === 'InProgress'? (
  701. <Button
  702. variant="contained"
  703. color="warning"
  704. startIcon={<PauseIcon />}
  705. onClick={() => handleOpenReasonModel()}
  706. >
  707. {t("Pause")}
  708. </Button>
  709. ) : (
  710. <Button
  711. variant="contained"
  712. color="success"
  713. startIcon={<PlayArrowIcon />}
  714. onClick={handleResume}
  715. >
  716. {t("Continue")}
  717. </Button>
  718. )}
  719. <Button
  720. sx={{ mt: 2, alignSelf: "flex-end" }}
  721. variant="outlined"
  722. disabled={lineDetail?.status === 'Paused'}
  723. onClick={() => setShowOutputTable(true)}
  724. >
  725. {t("Order Complete")}
  726. </Button>
  727. </Stack>
  728. </CardContent>
  729. </Card>
  730. </Grid>
  731. </Grid>
  732. )}
  733. {/* ========== 产出输入表单 ========== */}
  734. {showOutputTable && (
  735. <Box>
  736. <Paper sx={{ p: 3, bgcolor: 'grey.50' }}>
  737. <Table size="small">
  738. <TableHead>
  739. <TableRow>
  740. <TableCell width="25%" align="center">{t("Type")}</TableCell>
  741. <TableCell width="25%" align="center">{t("Quantity")}</TableCell>
  742. <TableCell width="25%" align="center">{t("Unit")}</TableCell>
  743. <TableCell width="25%" align="center">{t(" ")}</TableCell>
  744. </TableRow>
  745. </TableHead>
  746. <TableBody>
  747. <TableRow>
  748. <TableCell>
  749. <Typography fontWeight={500}>{t("Output from Process")}</Typography>
  750. </TableCell>
  751. <TableCell>
  752. <TextField
  753. type="number"
  754. fullWidth
  755. size="small"
  756. value={outputData.outputFromProcessQty}
  757. onChange={(e) => setOutputData({
  758. ...outputData,
  759. outputFromProcessQty: parseInt(e.target.value) || 0
  760. })}
  761. />
  762. </TableCell>
  763. <TableCell>
  764. <Select
  765. fullWidth
  766. size="small"
  767. value={outputData.outputFromProcessUom}
  768. onChange={(e) => setOutputData({
  769. ...outputData,
  770. outputFromProcessUom: e.target.value
  771. })}
  772. displayEmpty
  773. >
  774. <MenuItem value="">
  775. <em>{t("Select Unit")}</em>
  776. </MenuItem>
  777. {uomList.map((uom) => (
  778. <MenuItem key={uom} value={uom}>
  779. {uom}
  780. </MenuItem>
  781. ))}
  782. </Select>
  783. </TableCell>
  784. <TableCell>
  785. <Typography fontSize={15} align="center"> <strong>{t("Description")}</strong></Typography>
  786. </TableCell>
  787. </TableRow>
  788. <TableRow sx={{ bgcolor: 'warning.50' }}>
  789. <TableCell>
  790. <Typography fontWeight={500} color="warning.dark">{t("Defect")}{t("(1)")}</Typography>
  791. </TableCell>
  792. <TableCell>
  793. <TextField
  794. type="number"
  795. fullWidth
  796. size="small"
  797. value={outputData.defectQty}
  798. onChange={(e) => setOutputData({
  799. ...outputData,
  800. defectQty: parseInt(e.target.value) || 0
  801. })}
  802. />
  803. </TableCell>
  804. <TableCell>
  805. <Select
  806. fullWidth
  807. size="small"
  808. value={outputData.defectUom}
  809. onChange={(e) => setOutputData({
  810. ...outputData,
  811. defectUom: e.target.value
  812. })}
  813. displayEmpty
  814. >
  815. <MenuItem value="">
  816. <em>{t("Select Unit")}</em>
  817. </MenuItem>
  818. {uomList.map((uom) => (
  819. <MenuItem key={uom} value={uom}>
  820. {uom}
  821. </MenuItem>
  822. ))}
  823. </Select>
  824. </TableCell>
  825. <TableCell>
  826. <TextField
  827. fullWidth
  828. size="small"
  829. onChange={(e) => setOutputData({
  830. ...outputData,
  831. defectDescription: e.target.value
  832. })}
  833. />
  834. </TableCell>
  835. </TableRow>
  836. <TableRow sx={{ bgcolor: 'warning.50' }}>
  837. <TableCell>
  838. <Typography fontWeight={500} color="warning.dark">{t("Defect")}{t("(2)")}</Typography>
  839. </TableCell>
  840. <TableCell>
  841. <TextField
  842. type="number"
  843. fullWidth
  844. size="small"
  845. value={outputData.defect2Qty}
  846. onChange={(e) => setOutputData({
  847. ...outputData,
  848. defect2Qty: parseInt(e.target.value) || 0
  849. })}
  850. />
  851. </TableCell>
  852. <TableCell>
  853. <Select
  854. fullWidth
  855. size="small"
  856. value={outputData.defect2Uom}
  857. onChange={(e) => setOutputData({
  858. ...outputData,
  859. defect2Uom: e.target.value
  860. })}
  861. displayEmpty
  862. >
  863. <MenuItem value="">
  864. <em>{t("Select Unit")}</em>
  865. </MenuItem>
  866. {uomList.map((uom) => (
  867. <MenuItem key={uom} value={uom}>
  868. {uom}
  869. </MenuItem>
  870. ))}
  871. </Select>
  872. </TableCell>
  873. <TableCell>
  874. <TextField
  875. fullWidth
  876. size="small"
  877. onChange={(e) => setOutputData({
  878. ...outputData,
  879. defectDescription2: e.target.value
  880. })}
  881. />
  882. </TableCell>
  883. </TableRow>
  884. <TableRow sx={{ bgcolor: 'warning.50' }}>
  885. <TableCell>
  886. <Typography fontWeight={500} color="warning.dark">{t("Defect")}{t("(3)")}</Typography>
  887. </TableCell>
  888. <TableCell>
  889. <TextField
  890. type="number"
  891. fullWidth
  892. size="small"
  893. value={outputData.defect3Qty}
  894. onChange={(e) => setOutputData({
  895. ...outputData,
  896. defect3Qty: parseInt(e.target.value) || 0
  897. })}
  898. />
  899. </TableCell>
  900. <TableCell>
  901. <Select
  902. fullWidth
  903. size="small"
  904. value={outputData.defect3Uom}
  905. onChange={(e) => setOutputData({
  906. ...outputData,
  907. defect3Uom: e.target.value
  908. })}
  909. displayEmpty
  910. >
  911. <MenuItem value="">
  912. <em>{t("Select Unit")}</em>
  913. </MenuItem>
  914. {uomList.map((uom) => (
  915. <MenuItem key={uom} value={uom}>
  916. {uom}
  917. </MenuItem>
  918. ))}
  919. </Select>
  920. </TableCell>
  921. <TableCell>
  922. <TextField
  923. fullWidth
  924. size="small"
  925. onChange={(e) => setOutputData({
  926. ...outputData,
  927. defectDescription3: e.target.value
  928. })}
  929. />
  930. </TableCell>
  931. </TableRow>
  932. <TableRow sx={{ bgcolor: 'error.50' }}>
  933. <TableCell>
  934. <Typography fontWeight={500} color="error.dark">{t("Scrap")}</Typography>
  935. </TableCell>
  936. <TableCell>
  937. <TextField
  938. type="number"
  939. fullWidth
  940. size="small"
  941. value={outputData.scrapQty}
  942. onChange={(e) => setOutputData({
  943. ...outputData,
  944. scrapQty: parseInt(e.target.value) || 0
  945. })}
  946. />
  947. </TableCell>
  948. <TableCell>
  949. <Select
  950. fullWidth
  951. size="small"
  952. value={outputData.scrapUom}
  953. onChange={(e) => setOutputData({
  954. ...outputData,
  955. scrapUom: e.target.value
  956. })}
  957. displayEmpty
  958. >
  959. <MenuItem value="">
  960. <em>{t("Select Unit")}</em>
  961. </MenuItem>
  962. {uomList.map((uom) => (
  963. <MenuItem key={uom} value={uom}>
  964. {uom}
  965. </MenuItem>
  966. ))}
  967. </Select>
  968. </TableCell>
  969. </TableRow>
  970. </TableBody>
  971. </Table>
  972. <Box sx={{ mt: 3, display: 'flex', gap: 2 }}>
  973. <Button
  974. variant="outlined"
  975. onClick={() => setShowOutputTable(false)}
  976. >
  977. {t("Cancel")}
  978. </Button>
  979. <Button
  980. variant="contained"
  981. startIcon={<CheckCircleIcon />}
  982. onClick={handleSubmitOutput}
  983. >
  984. {t("Complete Step")}
  985. </Button>
  986. </Box>
  987. </Paper>
  988. </Box>
  989. )}
  990. {/* ========== Bag Consumption Form ========== */}
  991. {((showOutputTable || isCompleted) && isPackagingProcess && jobOrderId && lineId) && (
  992. <BagConsumptionForm
  993. jobOrderId={jobOrderId}
  994. lineId={lineId}
  995. bomDescription={processData?.bomDescription}
  996. processName={lineDetail?.name}
  997. submitedBagRecord={lineDetail?.submitedBagRecord}
  998. onRefresh={handleRefreshLineDetail}
  999. />
  1000. )}
  1001. </>
  1002. )}
  1003. <Dialog
  1004. open={isOpenReasonModel}
  1005. onClose={handleCloseReasonModel}
  1006. maxWidth="sm"
  1007. fullWidth
  1008. >
  1009. <DialogTitle>{t("Pause Reason")}</DialogTitle>
  1010. <DialogContent>
  1011. <TextField
  1012. autoFocus
  1013. margin="dense"
  1014. label={t("Reason")}
  1015. fullWidth
  1016. multiline
  1017. rows={4}
  1018. value={pauseReason}
  1019. onChange={(e) => setPauseReason(e.target.value)}
  1020. />
  1021. </DialogContent>
  1022. <DialogActions>
  1023. <Button onClick={handleCloseReasonModel}>
  1024. {t("Cancel")}
  1025. </Button>
  1026. <Button
  1027. onClick={handleSaveReason}
  1028. variant="contained"
  1029. disabled={!pauseReason.trim()}
  1030. >
  1031. {t("Confirm")}
  1032. </Button>
  1033. </DialogActions>
  1034. </Dialog>
  1035. </Box>
  1036. );
  1037. };
  1038. export default ProductionProcessStepExecution;