FPSMS-frontend
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

PoDetail.tsx 15 KiB

6ヶ月前
6ヶ月前
6ヶ月前
6ヶ月前
6ヶ月前
6ヶ月前
6ヶ月前
6ヶ月前
4ヶ月前
4ヶ月前
4ヶ月前
4ヶ月前
6ヶ月前
4ヶ月前
4ヶ月前
4ヶ月前
6ヶ月前
4ヶ月前
4ヶ月前
4ヶ月前
4ヶ月前
6ヶ月前
4ヶ月前
4ヶ月前
6ヶ月前
4ヶ月前
6ヶ月前
4ヶ月前
6ヶ月前
4ヶ月前
6ヶ月前
4ヶ月前
4ヶ月前
6ヶ月前
4ヶ月前
4ヶ月前
6ヶ月前
6ヶ月前
6ヶ月前
6ヶ月前
6ヶ月前
6ヶ月前
4ヶ月前
4ヶ月前
4ヶ月前
4ヶ月前
4ヶ月前
4ヶ月前
4ヶ月前
6ヶ月前
6ヶ月前
4ヶ月前
4ヶ月前
4ヶ月前
6ヶ月前
6ヶ月前
4ヶ月前
6ヶ月前
4ヶ月前
6ヶ月前
4ヶ月前
6ヶ月前
6ヶ月前
6ヶ月前
4ヶ月前
6ヶ月前
4ヶ月前
4ヶ月前
4ヶ月前
6ヶ月前
6ヶ月前
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  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;