FPSMS-frontend
25'ten fazla konu seçemezsiniz Konular bir harf veya rakamla başlamalı, kısa çizgiler ('-') içerebilir ve en fazla 35 karakter uzunluğunda olabilir.
 
 

301 satır
11 KiB

  1. /*
  2. "use client";
  3. import React, { useCallback, useEffect, useState } from "react";
  4. import {
  5. Box,
  6. Button,
  7. Card,
  8. CardContent,
  9. CardActions,
  10. Stack,
  11. Typography,
  12. Chip,
  13. CircularProgress,
  14. Grid,
  15. } from "@mui/material";
  16. import ArrowBackIcon from "@mui/icons-material/ArrowBack";
  17. import { useTranslation } from "react-i18next";
  18. import { fetchAllJoPickOrders, AllJoPickOrderResponse } from "@/app/api/jo/actions";
  19. import JobPickExecution from "./newJobPickExecution";
  20. interface Props {
  21. onSwitchToRecordTab?: () => void;
  22. }
  23. const JoPickOrderList: React.FC<Props> = ({ onSwitchToRecordTab }) =>{
  24. const { t } = useTranslation(["common", "jo"]);
  25. const [loading, setLoading] = useState(false);
  26. const [pickOrders, setPickOrders] = useState<AllJoPickOrderResponse[]>([]);
  27. const [selectedPickOrderId, setSelectedPickOrderId] = useState<number | undefined>(undefined);
  28. const [selectedJobOrderId, setSelectedJobOrderId] = useState<number | undefined>(undefined);
  29. type PickOrderFilter = "all" | "drink" | "Powder_Mixture" | "other";
  30. const [filter, setFilter] = useState<PickOrderFilter>("all");
  31. type FloorFilter = "ALL" | "2F" | "3F" | "4F" | "NO_LOT";
  32. const [floorFilter, setFloorFilter] = useState<FloorFilter>("ALL");
  33. const fetchPickOrders = useCallback(async () => {
  34. setLoading(true);
  35. try {
  36. const typeParam = filter === "all" ? undefined : filter;
  37. const floorParam = floorFilter === "ALL" ? undefined : floorFilter;
  38. const data = await fetchAllJoPickOrders(typeParam, floorParam);
  39. setPickOrders(Array.isArray(data) ? data : []);
  40. } catch (e) {
  41. console.error(e);
  42. setPickOrders([]);
  43. } finally {
  44. setLoading(false);
  45. }
  46. }, [filter, floorFilter]);
  47. useEffect(() => {
  48. fetchPickOrders();
  49. }, [fetchPickOrders, filter, floorFilter]);
  50. const handleBackToList = useCallback(() => {
  51. setSelectedPickOrderId(undefined);
  52. setSelectedJobOrderId(undefined);
  53. fetchPickOrders();
  54. }, [fetchPickOrders]);
  55. // If a pick order is selected, show JobPickExecution detail view
  56. if (selectedPickOrderId !== undefined) {
  57. return (
  58. <Box>
  59. <Box sx={{ mb: 2 }}>
  60. <Button
  61. variant="outlined"
  62. onClick={handleBackToList}
  63. startIcon={<ArrowBackIcon />}
  64. >
  65. {t("Back to List")}
  66. </Button>
  67. </Box>
  68. <JobPickExecution
  69. filterArgs={{ pickOrderId: selectedPickOrderId, jobOrderId: selectedJobOrderId }}
  70. //onSwitchToRecordTab={onSwitchToRecordTab}
  71. onBackToList={handleBackToList} // 传递新的回调
  72. />
  73. </Box>
  74. );
  75. }
  76. return (
  77. <Box>
  78. {loading ? (
  79. <Box sx={{ display: "flex", justifyContent: "center", p: 3 }}>
  80. <CircularProgress />
  81. </Box>
  82. ) : (
  83. <Box>
  84. <Box sx={{ display: 'flex', gap: 1, alignItems: 'center', flexWrap: 'wrap', mb: 2 }}>
  85. <Button
  86. variant={filter === 'all' ? 'contained' : 'outlined'}
  87. size="small"
  88. onClick={() => setFilter('all')}
  89. >
  90. {t("All")}
  91. </Button>
  92. <Button
  93. variant={filter === 'drink' ? 'contained' : 'outlined'}
  94. size="small"
  95. onClick={() => setFilter('drink')}
  96. >
  97. {t("Drink")}
  98. </Button>
  99. <Button
  100. variant={filter === 'Powder_Mixture' ? 'contained' : 'outlined'}
  101. size="small"
  102. onClick={() => setFilter('Powder_Mixture')}
  103. >
  104. {t("Powder Mixture")}
  105. </Button>
  106. <Button
  107. variant={filter === 'other' ? 'contained' : 'outlined'}
  108. size="small"
  109. onClick={() => setFilter('other')}
  110. >
  111. {t("Other")}
  112. </Button>
  113. </Box>
  114. <Box sx={{ display: 'flex', gap: 1, alignItems: 'center', flexWrap: 'wrap', mb: 2 }}>
  115. <Button
  116. variant={floorFilter === "ALL" ? "contained" : "outlined"}
  117. size="small"
  118. onClick={() => setFloorFilter("ALL")}
  119. >
  120. {t("Select All")}
  121. </Button>
  122. <Button
  123. variant={floorFilter === "2F" ? "contained" : "outlined"}
  124. size="small"
  125. onClick={() => setFloorFilter("2F")}
  126. >
  127. 2F
  128. </Button>
  129. <Button
  130. variant={floorFilter === "3F" ? "contained" : "outlined"}
  131. size="small"
  132. onClick={() => setFloorFilter("3F")}
  133. >
  134. 3F
  135. </Button>
  136. <Button
  137. variant={floorFilter === "4F" ? "contained" : "outlined"}
  138. size="small"
  139. onClick={() => setFloorFilter("4F")}
  140. >
  141. 4F
  142. </Button>
  143. <Button
  144. variant={floorFilter === "NO_LOT" ? "contained" : "outlined"}
  145. size="small"
  146. onClick={() => setFloorFilter("NO_LOT")}
  147. >
  148. {t("No Lot")}
  149. </Button>
  150. </Box>
  151. <Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
  152. {t("Total pick orders")}: {pickOrders.length}
  153. </Typography>
  154. <Grid container spacing={2}>
  155. {pickOrders.map((pickOrder) => {
  156. const status = String(pickOrder.jobOrderStatus || "");
  157. const statusLower = status.toLowerCase();
  158. const statusColor =
  159. statusLower === "completed"
  160. ? "success"
  161. : statusLower === "pending" || statusLower === "processing"
  162. ? "primary"
  163. : "default";
  164. const finishedCount = pickOrder.finishedPickOLineCount ?? 0;
  165. return (
  166. <Grid key={pickOrder.id} item xs={12} sm={6} md={4}>
  167. <Card
  168. sx={{
  169. minHeight: 180,
  170. maxHeight: 280,
  171. display: "flex",
  172. flexDirection: "column",
  173. }}
  174. >
  175. <CardContent
  176. sx={{
  177. pb: 1,
  178. flexGrow: 1,
  179. overflow: "auto",
  180. }}
  181. >
  182. <Stack direction="row" justifyContent="space-between" alignItems="center">
  183. <Box sx={{ minWidth: 0 }}>
  184. <Typography variant="subtitle1">
  185. {t("Job Order")}: {pickOrder.jobOrderCode || "-"}
  186. </Typography>
  187. </Box>
  188. <Chip size="small" label={t(status)} color={statusColor as any} />
  189. </Stack>
  190. <Typography variant="body2" color="text.secondary">
  191. {t("Lot No")}: {pickOrder.lotNo || "-"}
  192. </Typography>
  193. <Typography variant="body2" color="text.secondary">
  194. {t("Pick Order")}: {pickOrder.pickOrderCode || "-"}
  195. </Typography>
  196. <Typography variant="body2" color="text.secondary">
  197. {t("Item Name")}: {pickOrder.itemName}
  198. </Typography>
  199. <Typography variant="body2" color="text.secondary">
  200. {t("Required Qty")}: {pickOrder.reqQty} ({pickOrder.uomName})
  201. </Typography>
  202. {floorFilter === "ALL" ? (
  203. <>
  204. {pickOrder.floorPickCounts?.map(({ floor, finishedCount, totalCount }) => (
  205. <Typography
  206. key={floor}
  207. variant="body2"
  208. color="text.secondary"
  209. component="span"
  210. sx={{ mr: 1 }}
  211. >
  212. {floor}: {finishedCount}/{totalCount}
  213. </Typography>
  214. ))}
  215. {!!pickOrder.noLotPickCount && (
  216. <Typography
  217. key="NO_LOT"
  218. variant="body2"
  219. color="text.secondary"
  220. component="span"
  221. sx={{ mr: 1 }}
  222. >
  223. {t("No Lot")}: {pickOrder.noLotPickCount.finishedCount}/{pickOrder.noLotPickCount.totalCount}
  224. </Typography>
  225. )}
  226. </>
  227. ) : floorFilter === "NO_LOT" ? (
  228. !!pickOrder.noLotPickCount && (
  229. <Typography
  230. key="NO_LOT"
  231. variant="body2"
  232. color="text.secondary"
  233. component="span"
  234. sx={{ mr: 1 }}
  235. >
  236. {t("No Lot")}: {pickOrder.noLotPickCount.finishedCount}/{pickOrder.noLotPickCount.totalCount}
  237. </Typography>
  238. )
  239. ) : (
  240. pickOrder.floorPickCounts
  241. ?.filter((c) => c.floor === floorFilter)
  242. .map(({ floor, finishedCount, totalCount }) => (
  243. <Typography
  244. key={floor}
  245. variant="body2"
  246. color="text.secondary"
  247. component="span"
  248. sx={{ mr: 1 }}
  249. >
  250. {floor}: {finishedCount}/{totalCount}
  251. </Typography>
  252. ))
  253. )}
  254. {typeof pickOrder.suggestedFailCount === "number" && pickOrder.suggestedFailCount > 0 && (
  255. <Typography variant="body2" color="error" sx={{ mt: 0.5 }}>
  256. {t("Suggested Fail")}: {pickOrder.suggestedFailCount}
  257. </Typography>
  258. )}
  259. {statusLower !== "pending" && finishedCount > 0 && (
  260. <Box sx={{ mt: 1 }}>
  261. <Typography variant="body2" fontWeight={600}>
  262. {t("Finished lines")}: {finishedCount}
  263. </Typography>
  264. </Box>
  265. )}
  266. </CardContent>
  267. <CardActions sx={{ pt: 0.5 }}>
  268. <Button
  269. variant="contained"
  270. size="small"
  271. onClick={() => {
  272. setSelectedPickOrderId(pickOrder.pickOrderId ?? undefined);
  273. setSelectedJobOrderId(pickOrder.jobOrderId ?? undefined);
  274. }}
  275. >
  276. {t("View Details")}
  277. </Button>
  278. <Box sx={{ flex: 1 }} />
  279. </CardActions>
  280. </Card>
  281. </Grid>
  282. );
  283. })}
  284. </Grid>
  285. </Box>
  286. )}
  287. </Box>
  288. );
  289. };
  290. export default JoPickOrderList;
  291. */