FPSMS-frontend
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

456 line
15 KiB

  1. "use client";
  2. import {
  3. fetchPoWithStockInLines,
  4. PoResult,
  5. PurchaseOrderLine,
  6. StockInLine,
  7. } from "@/app/api/po";
  8. import {
  9. Box,
  10. Button,
  11. ButtonProps,
  12. Collapse,
  13. Grid,
  14. IconButton,
  15. Paper,
  16. Stack,
  17. Tab,
  18. Table,
  19. TableBody,
  20. TableCell,
  21. TableContainer,
  22. TableHead,
  23. TableRow,
  24. Tabs,
  25. TabsProps,
  26. TextField,
  27. Typography,
  28. } from "@mui/material";
  29. import { useTranslation } from "react-i18next";
  30. // import InputDataGrid, { TableRow } from "../InputDataGrid/InputDataGrid";
  31. import {
  32. GridColDef,
  33. GridRowId,
  34. GridRowModel,
  35. useGridApiRef,
  36. } from "@mui/x-data-grid";
  37. import {
  38. checkPolAndCompletePo,
  39. fetchPoInClient,
  40. fetchStockInLineInfo,
  41. PurchaseQcResult,
  42. startPo,
  43. } from "@/app/api/po/actions";
  44. import {
  45. useCallback,
  46. useContext,
  47. useEffect,
  48. useMemo,
  49. useState,
  50. } from "react";
  51. import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
  52. import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp";
  53. import PoInputGrid from "./PoInputGrid";
  54. import { QcItemWithChecks } from "@/app/api/qc";
  55. import { useSearchParams } from "next/navigation";
  56. import { WarehouseResult } from "@/app/api/warehouse";
  57. import { calculateWeight, returnWeightUnit } from "@/app/utils/formatUtil";
  58. import { CameraContext } from "../Cameras/CameraProvider";
  59. import PoQcStockInModal from "./PoQcStockInModal";
  60. import QrModal from "./QrModal";
  61. import { PlayArrow } from "@mui/icons-material";
  62. import DoneIcon from "@mui/icons-material/Done";
  63. import { getCustomWidth } from "@/app/utils/commonUtil";
  64. import PoInfoCard from "./PoInfoCard";
  65. import { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil";
  66. type Props = {
  67. po: PoResult;
  68. qc: QcItemWithChecks[];
  69. warehouse: WarehouseResult[];
  70. };
  71. type EntryError =
  72. | {
  73. [field in keyof StockInLine]?: string;
  74. }
  75. | undefined;
  76. // type PolRow = TableRow<Partial<StockInLine>, EntryError>;
  77. const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => {
  78. const cameras = useContext(CameraContext);
  79. console.log(cameras);
  80. const { t } = useTranslation("purchaseOrder");
  81. const apiRef = useGridApiRef();
  82. const [purchaseOrder, setPurchaseOrder] = useState({ ...po });
  83. const [rows, setRows] = useState<PurchaseOrderLine[]>(
  84. purchaseOrder.pol || [],
  85. );
  86. const searchParams = useSearchParams();
  87. // const [currPoStatus, setCurrPoStatus] = useState(purchaseOrder.status);
  88. const removeParam = (paramToRemove: string) => {
  89. const newParams = new URLSearchParams(searchParams.toString());
  90. newParams.delete(paramToRemove);
  91. window.history.replaceState({}, '', `${window.location.pathname}?${newParams}`);
  92. };
  93. const handleCompletePo = useCallback(async () => {
  94. const checkRes = await checkPolAndCompletePo(purchaseOrder.id);
  95. console.log(checkRes);
  96. const newPo = await fetchPoInClient(purchaseOrder.id);
  97. setPurchaseOrder(newPo);
  98. }, [purchaseOrder.id]);
  99. const handleStartPo = useCallback(async () => {
  100. const startRes = await startPo(purchaseOrder.id);
  101. console.log(startRes);
  102. const newPo = await fetchPoInClient(purchaseOrder.id);
  103. setPurchaseOrder(newPo);
  104. }, [purchaseOrder.id]);
  105. useEffect(() => {
  106. setRows(purchaseOrder.pol || []);
  107. }, [purchaseOrder]);
  108. function Row(props: { row: PurchaseOrderLine }) {
  109. const { row } = props;
  110. const [firstReceiveQty, setFirstReceiveQty] = useState<number>()
  111. const [secondReceiveQty, setSecondReceiveQty] = useState<number>()
  112. const [open, setOpen] = useState(false);
  113. const [processedQty, setProcessedQty] = useState(row.processed);
  114. const [currStatus, setCurrStatus] = useState(row.status);
  115. const [stockInLine, setStockInLine] = useState(row.stockInLine);
  116. const totalWeight = useMemo(
  117. () => calculateWeight(row.qty, row.uom),
  118. [row.qty, row.uom],
  119. );
  120. const weightUnit = useMemo(
  121. () => returnWeightUnit(row.uom),
  122. [row.uom],
  123. );
  124. useEffect(() => {
  125. if (processedQty === row.qty) {
  126. setCurrStatus("completed".toUpperCase());
  127. } else if (processedQty > 0) {
  128. setCurrStatus("receiving".toUpperCase());
  129. } else {
  130. setCurrStatus("pending".toUpperCase());
  131. }
  132. }, [processedQty, row.qty]);
  133. return (
  134. <>
  135. <TableRow sx={{ "& > *": { borderBottom: "unset" }, color: "black" }}>
  136. <TableCell>
  137. <IconButton
  138. disabled={purchaseOrder.status.toLowerCase() === "pending"}
  139. aria-label="expand row"
  140. size="small"
  141. onClick={() => setOpen(!open)}
  142. >
  143. {open ? <KeyboardArrowUpIcon /> : <KeyboardArrowDownIcon />}
  144. </IconButton>
  145. </TableCell>
  146. <TableCell align="left">{row.itemNo}</TableCell>
  147. <TableCell align="left">{row.itemName}</TableCell>
  148. <TableCell align="left">{integerFormatter.format(row.qty)}</TableCell>
  149. <TableCell align="left">{integerFormatter.format(processedQty)}</TableCell>
  150. <TableCell align="left">{row.uom?.code}</TableCell>
  151. <TableCell align="left">
  152. {decimalFormatter.format(totalWeight)} {weightUnit}
  153. </TableCell>
  154. {/* <TableCell align="left">{weightUnit}</TableCell> */}
  155. <TableCell align="left">{decimalFormatter.format(row.price)}</TableCell>
  156. {/* <TableCell align="left">{row.expiryDate}</TableCell> */}
  157. <TableCell align="left">{t(`${currStatus.toLowerCase()}`)}</TableCell>
  158. {/* <TableCell align="left">
  159. 0
  160. </TableCell>
  161. <TableCell align="left">
  162. <TextField
  163. label="輸入數量"
  164. type="text" // Use type="text" to allow validation in the change handler
  165. variant="outlined"
  166. value={secondReceiveQty}
  167. // onChange={handleChange}
  168. InputProps={{
  169. inputProps: {
  170. min: 0, // Optional: set a minimum value
  171. step: 1 // Optional: set the step for the number input
  172. }
  173. }}
  174. />
  175. </TableCell> */}
  176. </TableRow>
  177. <TableRow>
  178. {/* <TableCell /> */}
  179. <TableCell style={{ paddingBottom: 0, paddingTop: 0 }} colSpan={12}>
  180. <Collapse in={true} timeout="auto" unmountOnExit>
  181. {/* <Collapse in={open} timeout="auto" unmountOnExit> */}
  182. <Table>
  183. <TableBody>
  184. <TableRow>
  185. <TableCell align="right">
  186. <Box>
  187. <PoInputGrid
  188. qc={qc}
  189. setRows={setRows}
  190. stockInLine={stockInLine}
  191. setStockInLine={setStockInLine}
  192. setProcessedQty={setProcessedQty}
  193. itemDetail={row}
  194. warehouse={warehouse}
  195. />
  196. </Box>
  197. </TableCell>
  198. </TableRow>
  199. </TableBody>
  200. </Table>
  201. </Collapse>
  202. </TableCell>
  203. </TableRow>
  204. </>
  205. );
  206. }
  207. const [tabIndex, setTabIndex] = useState(0);
  208. const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
  209. (_e, newValue) => {
  210. setTabIndex(newValue);
  211. },
  212. [],
  213. );
  214. const [isOpenScanner, setOpenScanner] = useState(false);
  215. const onOpenScanner = useCallback(() => {
  216. setOpenScanner(true);
  217. }, []);
  218. const onCloseScanner = useCallback(() => {
  219. setOpenScanner(false);
  220. }, []);
  221. const [itemInfo, setItemInfo] = useState<
  222. StockInLine & { warehouseId?: number }
  223. >();
  224. const [putAwayOpen, setPutAwayOpen] = useState(false);
  225. // const [scannedInfo, setScannedInfo] = useState<QrCodeInfo>({} as QrCodeInfo);
  226. const closePutAwayModal = useCallback(() => {
  227. setPutAwayOpen(false);
  228. setItemInfo(undefined);
  229. }, []);
  230. const openPutAwayModal = useCallback(() => {
  231. setPutAwayOpen(true);
  232. }, []);
  233. const buttonData = useMemo(() => {
  234. switch (purchaseOrder.status.toLowerCase()) {
  235. case "pending":
  236. return {
  237. buttonName: "start",
  238. title: t("Do you want to start?"),
  239. confirmButtonText: t("Start"),
  240. successTitle: t("Start Success"),
  241. errorTitle: t("Start Fail"),
  242. buttonText: t("Start PO"),
  243. buttonIcon: <PlayArrow />,
  244. buttonColor: "success",
  245. disabled: false,
  246. onClick: handleStartPo,
  247. };
  248. case "receiving":
  249. return {
  250. buttonName: "complete",
  251. title: t("Do you want to complete?"),
  252. confirmButtonText: t("Complete"),
  253. successTitle: t("Complete Success"),
  254. errorTitle: t("Complete Fail"),
  255. buttonText: t("Complete PO"),
  256. buttonIcon: <DoneIcon />,
  257. buttonColor: "info",
  258. disabled: false,
  259. onClick: handleCompletePo,
  260. };
  261. default:
  262. return {
  263. buttonName: "complete",
  264. title: t("Do you want to complete?"),
  265. confirmButtonText: t("Complete"),
  266. successTitle: t("Complete Success"),
  267. errorTitle: t("Complete Fail"),
  268. buttonText: t("Complete PO"),
  269. buttonIcon: <DoneIcon />,
  270. buttonColor: "info",
  271. disabled: true,
  272. };
  273. // break;
  274. }
  275. }, [purchaseOrder.status, t, handleStartPo, handleCompletePo]);
  276. const FIRST_IN_FIELD = "firstInQty"
  277. const SECOND_IN_FIELD = "secondInQty"
  278. const renderFieldCondition = useCallback((field: "firstInQty" | "secondInQty"): boolean => {
  279. switch (field) {
  280. case FIRST_IN_FIELD:
  281. return true;
  282. case SECOND_IN_FIELD:
  283. return true;
  284. default:
  285. return false; // Default case
  286. }
  287. }, []);
  288. return (
  289. <>
  290. <Stack
  291. spacing={2}
  292. // component="form"
  293. // onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)}
  294. >
  295. <Grid container xs={12} justifyContent="start">
  296. <Grid item>
  297. <Typography mb={2} variant="h4">
  298. {/* {purchaseOrder.code} - {currPoStatus} */}
  299. {purchaseOrder.code} -{" "}
  300. {t(`${purchaseOrder.status.toLowerCase()}`)}
  301. </Typography>
  302. </Grid>
  303. </Grid>
  304. {false ? (<Grid container xs={12} justifyContent="start">
  305. <Grid item xs={3}>
  306. <TextField
  307. label={t("dnNo")}
  308. type="text" // Use type="text" to allow validation in the change handler
  309. variant="outlined"
  310. // value={secondReceiveQty}
  311. // onChange={handleChange}
  312. InputProps={{
  313. inputProps: {
  314. min: 0, // Optional: set a minimum value
  315. step: 1 // Optional: set the step for the number input
  316. }
  317. }}
  318. />
  319. </Grid>
  320. <Grid item xs={3}>
  321. <TextField
  322. label={t("dnDate")}
  323. type="text" // Use type="text" to allow validation in the change handler
  324. variant="outlined"
  325. defaultValue={"07/08/2025"}
  326. // value={secondReceiveQty}
  327. // onChange={handleChange}
  328. InputProps={{
  329. inputProps: {
  330. min: 0, // Optional: set a minimum value
  331. step: 1 // Optional: set the step for the number input
  332. }
  333. }}
  334. />
  335. {/* <Button
  336. onClick={buttonData.onClick}
  337. disabled={buttonData.disabled}
  338. color={buttonData.buttonColor as ButtonProps["color"]}
  339. startIcon={buttonData.buttonIcon}
  340. >
  341. {buttonData.buttonText}
  342. </Button> */}
  343. </Grid>
  344. <Grid
  345. item
  346. xs={6}
  347. display="flex"
  348. justifyContent="end"
  349. alignItems="end"
  350. >
  351. <Button onClick={onOpenScanner}>{t("Accept submit")}</Button>
  352. </Grid>
  353. </Grid>) : undefined}
  354. <Grid container xs={12} justifyContent="space-between">
  355. {/* <Grid item xs={4}> */}
  356. {/* scanner */}
  357. {/* </Grid> */}
  358. <Grid
  359. item
  360. xs={4}
  361. display="flex"
  362. justifyContent="end"
  363. alignItems="end"
  364. >
  365. <QrModal
  366. open={isOpenScanner}
  367. onClose={onCloseScanner}
  368. warehouse={warehouse}
  369. />
  370. {/* <Button onClick={onOpenScanner}>{t("Accept submit")}</Button> */}
  371. {/* <Button onClick={onOpenScanner}>{t("bind")}</Button> */}
  372. </Grid>
  373. </Grid>
  374. {/* tab 1 */}
  375. <Grid rowSpacing={2} container sx={{ display: tabIndex === 0 ? "block" : "none" }}>
  376. <Grid item xs={12}>
  377. <PoInfoCard po={purchaseOrder} />
  378. </Grid>
  379. <Grid item xs={12}>
  380. <TableContainer component={Paper} sx={{ width: 'fit-content', overflow: 'auto' }}>
  381. {/* <TableContainer component={Paper} sx={{width: getCustomWidth(), overflow: 'auto' }}> */}
  382. <Table aria-label="collapsible table" stickyHeader>
  383. <TableHead>
  384. <TableRow>
  385. <TableCell /> {/* for the collapse button */}
  386. <TableCell>{t("itemNo")}</TableCell>
  387. <TableCell align="left">{t("itemName")}</TableCell>
  388. <TableCell align="left">{t("qty")}</TableCell>
  389. <TableCell align="left">{t("processed")}</TableCell>
  390. <TableCell align="left">{t("uom")}</TableCell>
  391. <TableCell align="left">{t("total weight")}</TableCell>
  392. {/* <TableCell align="left">{t("weight unit")}</TableCell> */}
  393. <TableCell align="left">{`${t("price")} ($)`}</TableCell>
  394. {/* <TableCell align="left">{t("expiryDate")}</TableCell> */}
  395. <TableCell align="left">{t("status")}</TableCell>
  396. {/* start == true && firstInQty == null ? no hide : hide*/}
  397. {/* {renderFieldCondition(FIRST_IN_FIELD) ? <TableCell align="left">{t("receivedQty")}</TableCell> : undefined} */}
  398. {/* start == true && firstInQty == null ? hide and disabled : no hide*/}
  399. {/* {renderFieldCondition(SECOND_IN_FIELD) ? <TableCell align="left">{t("dnQty")}</TableCell> : undefined} */}
  400. {/* <TableCell align="left">{"add icon button"}</TableCell> */}
  401. </TableRow>
  402. </TableHead>
  403. <TableBody>
  404. {rows.map((row) => (
  405. <Row key={row.id} row={row} />
  406. ))}
  407. </TableBody>
  408. </Table>
  409. </TableContainer>
  410. </Grid>
  411. </Grid>
  412. {/* tab 2 */}
  413. <Grid sx={{ display: tabIndex === 1 ? "block" : "none" }}>
  414. {/* <StyledDataGrid
  415. /> */}
  416. </Grid>
  417. </Stack>
  418. {itemInfo !== undefined && (
  419. <>
  420. <PoQcStockInModal
  421. type={"putaway"}
  422. open={putAwayOpen}
  423. warehouse={warehouse}
  424. setItemDetail={setItemInfo}
  425. onClose={closePutAwayModal}
  426. itemDetail={itemInfo}
  427. />
  428. </>
  429. )}
  430. </>
  431. );
  432. };
  433. export default PoDetail;