FPSMS-frontend
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
 
 

309 строки
9.5 KiB

  1. "use client";
  2. import React, { useState, useEffect, useCallback, useMemo } from "react";
  3. import {
  4. Box,
  5. Paper,
  6. Typography,
  7. Table,
  8. TableBody,
  9. TableCell,
  10. TableHead,
  11. TableRow,
  12. TextField,
  13. Select,
  14. MenuItem,
  15. Button,
  16. IconButton,
  17. CircularProgress,
  18. } from "@mui/material";
  19. import AddIcon from "@mui/icons-material/Add";
  20. import DeleteIcon from "@mui/icons-material/Delete";
  21. import { useTranslation } from "react-i18next";
  22. import {
  23. getBagInfo,
  24. createJoBagConsumption,
  25. GetBagInfoResponse,
  26. CreateJoBagConsumptionRequest,
  27. } from "@/app/api/bag/action";
  28. import { fetchProductProcessLineDetail } from "@/app/api/jo/actions";
  29. export interface BagConsumptionRow {
  30. bagId: number;
  31. bagLotLineId: number;
  32. consumedQty: number;
  33. scrapQty: number;
  34. }
  35. interface BagConsumptionFormProps {
  36. jobOrderId: number;
  37. lineId: number;
  38. bomDescription?: string;
  39. isLastLine: boolean;
  40. onRefresh?: () => void;
  41. }
  42. const BagConsumptionForm: React.FC<BagConsumptionFormProps> = ({
  43. jobOrderId,
  44. lineId,
  45. bomDescription,
  46. isLastLine,
  47. onRefresh,
  48. }) => {
  49. const { t } = useTranslation(["common", "jo"]);
  50. const [bagList, setBagList] = useState<GetBagInfoResponse[]>([]);
  51. const [bagConsumptionRows, setBagConsumptionRows] = useState<BagConsumptionRow[]>([
  52. { bagId: 0, bagLotLineId: 0, consumedQty: 0, scrapQty: 0 },
  53. ]);
  54. const [isLoadingBags, setIsLoadingBags] = useState(false);
  55. const [isSubmitting, setIsSubmitting] = useState(false);
  56. // 判断是否显示表单
  57. const shouldShow = useMemo(() => {
  58. return bomDescription === "FG" && isLastLine;
  59. }, [bomDescription, isLastLine]);
  60. // 加载 Bag 列表
  61. useEffect(() => {
  62. if (shouldShow) {
  63. setIsLoadingBags(true);
  64. getBagInfo()
  65. .then((bags) => {
  66. setBagList(bags);
  67. console.log("✅ Bag list loaded:", bags);
  68. })
  69. .catch((error) => {
  70. console.error("❌ Error loading bag list:", error);
  71. })
  72. .finally(() => {
  73. setIsLoadingBags(false);
  74. });
  75. }
  76. }, [shouldShow]);
  77. // 添加 Bag 行
  78. const handleAddBagRow = useCallback(() => {
  79. setBagConsumptionRows((prev) => [
  80. ...prev,
  81. { bagId: 0, bagLotLineId: 0, consumedQty: 0, scrapQty: 0 },
  82. ]);
  83. }, []);
  84. // 删除 Bag 行
  85. const handleDeleteBagRow = useCallback((index: number) => {
  86. setBagConsumptionRows((prev) => prev.filter((_, i) => i !== index));
  87. }, []);
  88. // 更新 Bag 行数据
  89. const handleBagRowChange = useCallback(
  90. (index: number, field: keyof BagConsumptionRow, value: any) => {
  91. setBagConsumptionRows((prev) =>
  92. prev.map((row, i) => (i === index ? { ...row, [field]: value } : row))
  93. );
  94. },
  95. []
  96. );
  97. // 当选择 bag 时,自动填充 bagLotLineId
  98. const handleBagSelect = useCallback(
  99. (index: number, bagLotLineId: number) => {
  100. const selectedBag = bagList.find((b) => b.id === bagLotLineId);
  101. if (selectedBag) {
  102. handleBagRowChange(index, "bagId", selectedBag.bagId);
  103. handleBagRowChange(index, "bagLotLineId", selectedBag.id);
  104. }
  105. },
  106. [bagList, handleBagRowChange]
  107. );
  108. // 提交 Bag Consumption
  109. const handleSubmitBagConsumption = useCallback(async () => {
  110. if (!jobOrderId || !lineId) {
  111. alert(t("Missing job order ID or line ID"));
  112. return;
  113. }
  114. try {
  115. setIsSubmitting(true);
  116. // 过滤掉未选择 bag 的行
  117. const validRows = bagConsumptionRows.filter(
  118. (row) => row.bagId > 0 && row.bagLotLineId > 0
  119. );
  120. if (validRows.length === 0) {
  121. alert(t("Please select at least one bag"));
  122. return;
  123. }
  124. // 提交每个 bag consumption
  125. const promises = validRows.map((row) => {
  126. const selectedBag = bagList.find((b) => b.id === row.bagLotLineId);
  127. const request: CreateJoBagConsumptionRequest = {
  128. bagId: row.bagId,
  129. bagLotLineId: row.bagLotLineId,
  130. jobId: jobOrderId,
  131. //startQty: selectedBag?.balanceQty || 0,
  132. consumedQty: row.consumedQty,
  133. scrapQty: row.scrapQty,
  134. };
  135. return createJoBagConsumption(request);
  136. });
  137. await Promise.all(promises);
  138. console.log("✅ Bag consumption submitted successfully");
  139. // 清空表单
  140. setBagConsumptionRows([
  141. { bagId: 0, bagLotLineId: 0, consumedQty: 0, scrapQty: 0 },
  142. ]);
  143. // 刷新 line detail
  144. if (onRefresh) {
  145. onRefresh();
  146. } else {
  147. const detail = await fetchProductProcessLineDetail(lineId);
  148. console.log("✅ Line detail refreshed:", detail);
  149. }
  150. } catch (error: any) {
  151. console.error("❌ Error submitting bag consumption:", error);
  152. // ✅ 显示更详细的错误信息
  153. const errorMessage = error?.message ||
  154. error?.response?.data?.message ||
  155. t("Failed to submit bag consumption. Please try again.");
  156. alert(errorMessage);
  157. } finally {
  158. setIsSubmitting(false);
  159. }
  160. }, [bagConsumptionRows, bagList, jobOrderId, lineId, onRefresh, t]);
  161. // 如果不满足显示条件,不渲染
  162. if (!shouldShow) {
  163. return null;
  164. }
  165. return (
  166. <Box sx={{ mt: 3 }}>
  167. <Paper sx={{ p: 3, bgcolor: "info.50" }}>
  168. <Typography variant="h6" gutterBottom>
  169. {t("Bag Consumption")}
  170. </Typography>
  171. {isLoadingBags ? (
  172. <Box sx={{ display: "flex", justifyContent: "center", p: 3 }}>
  173. <CircularProgress />
  174. </Box>
  175. ) : (
  176. <>
  177. <Table size="small" sx={{ mt: 2 }}>
  178. <TableHead>
  179. <TableRow>
  180. <TableCell width="40%">{t("Bag")}</TableCell>
  181. <TableCell width="25%" align="right">
  182. {t("Consumed Qty")}
  183. </TableCell>
  184. <TableCell width="25%" align="right">
  185. {t("Scrap Qty")}
  186. </TableCell>
  187. <TableCell width="10%" align="center">
  188. {t("Action")}
  189. </TableCell>
  190. </TableRow>
  191. </TableHead>
  192. <TableBody>
  193. {bagConsumptionRows.map((row, index) => (
  194. <TableRow key={index}>
  195. <TableCell>
  196. <Select
  197. fullWidth
  198. size="small"
  199. value={row.bagLotLineId || 0} // ✅ 改为使用 bagLotLineId
  200. onChange={(e) =>
  201. handleBagSelect(index, Number(e.target.value))
  202. }
  203. displayEmpty
  204. >
  205. <MenuItem value={0}>
  206. <em>{t("Select Bag")}</em>
  207. </MenuItem>
  208. {bagList.map((bag) => (
  209. <MenuItem key={bag.id} value={bag.id}> {/* ✅ 改为使用 bag.id (bagLotLineId) */}
  210. {bag.bagName} ({bag.code}) - {t("Balance")}:{" "}
  211. {bag.balanceQty}
  212. </MenuItem>
  213. ))}
  214. </Select>
  215. </TableCell>
  216. <TableCell align="right">
  217. <TextField
  218. type="number"
  219. size="small"
  220. fullWidth
  221. value={row.consumedQty}
  222. onChange={(e) =>
  223. handleBagRowChange(
  224. index,
  225. "consumedQty",
  226. Number(e.target.value) || 0
  227. )
  228. }
  229. inputProps={{ min: 0 }}
  230. />
  231. </TableCell>
  232. <TableCell align="right">
  233. <TextField
  234. type="number"
  235. size="small"
  236. fullWidth
  237. value={row.scrapQty}
  238. onChange={(e) =>
  239. handleBagRowChange(
  240. index,
  241. "scrapQty",
  242. Number(e.target.value) || 0
  243. )
  244. }
  245. inputProps={{ min: 0 }}
  246. />
  247. </TableCell>
  248. <TableCell align="center">
  249. {bagConsumptionRows.length > 1 && (
  250. <IconButton
  251. size="small"
  252. color="error"
  253. onClick={() => handleDeleteBagRow(index)}
  254. >
  255. <DeleteIcon />
  256. </IconButton>
  257. )}
  258. </TableCell>
  259. </TableRow>
  260. ))}
  261. </TableBody>
  262. </Table>
  263. <Box sx={{ mt: 2, display: "flex", gap: 2 }}>
  264. <Button
  265. variant="outlined"
  266. startIcon={<AddIcon />}
  267. onClick={handleAddBagRow}
  268. >
  269. {t("Select Another Bag Lot")}
  270. </Button>
  271. <Button
  272. variant="contained"
  273. color="primary"
  274. onClick={handleSubmitBagConsumption}
  275. disabled={isSubmitting}
  276. >
  277. {isSubmitting ? t("Submitting...") : t("Submit Bag Consumption")}
  278. </Button>
  279. </Box>
  280. </>
  281. )}
  282. </Paper>
  283. </Box>
  284. );
  285. };
  286. export default BagConsumptionForm;