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.
 
 
 

674 rivejä
19 KiB

  1. "use client";
  2. import {
  3. FooterPropsOverrides,
  4. GridActionsCellItem,
  5. GridCellParams,
  6. GridRowId,
  7. GridRowIdGetter,
  8. GridRowModel,
  9. GridRowModes,
  10. GridRowModesModel,
  11. GridToolbarContainer,
  12. useGridApiRef,
  13. } from "@mui/x-data-grid";
  14. import {
  15. Dispatch,
  16. MutableRefObject,
  17. SetStateAction,
  18. useCallback,
  19. useEffect,
  20. useMemo,
  21. useState,
  22. } from "react";
  23. import StyledDataGrid from "../StyledDataGrid";
  24. import { GridColDef } from "@mui/x-data-grid";
  25. import { Box, Button, Grid, Typography } from "@mui/material";
  26. import { useTranslation } from "react-i18next";
  27. import { Add } from "@mui/icons-material";
  28. import SaveIcon from "@mui/icons-material/Save";
  29. import DeleteIcon from "@mui/icons-material/Delete";
  30. import CancelIcon from "@mui/icons-material/Cancel";
  31. import FactCheckIcon from "@mui/icons-material/FactCheck";
  32. import ShoppingCartIcon from "@mui/icons-material/ShoppingCart";
  33. import { QcItemWithChecks } from "src/app/api/qc";
  34. import PlayArrowIcon from "@mui/icons-material/PlayArrow";
  35. import { PurchaseOrderLine, StockInLine } from "@/app/api/po";
  36. import { createStockInLine, testFetch } from "@/app/api/po/actions";
  37. import { useSearchParams } from "next/navigation";
  38. import { returnWeightUnit, calculateWeight, stockInLineStatusMap } from "@/app/utils/formatUtil";
  39. import PoQcStockInModal from "./PoQcStockInModal";
  40. import NotificationImportantIcon from "@mui/icons-material/NotificationImportant";
  41. import { WarehouseResult } from "@/app/api/warehouse";
  42. import LooksOneIcon from '@mui/icons-material/LooksOne';
  43. import LooksTwoIcon from '@mui/icons-material/LooksTwo';
  44. import Looks3Icon from '@mui/icons-material/Looks3';
  45. import axiosInstance from "@/app/(main)/axios/axiosInstance";
  46. // import axios, { AxiosRequestConfig } from "axios";
  47. import { NEXT_PUBLIC_API_URL } from "@/config/api";
  48. import qs from 'qs';
  49. import QrCodeIcon from '@mui/icons-material/QrCode';
  50. import { downloadFile } from "@/app/utils/commonUtil";
  51. import { fetchPoQrcode } from "@/app/api/pdf/actions";
  52. interface ResultWithId {
  53. id: number;
  54. }
  55. interface Props {
  56. qc: QcItemWithChecks[];
  57. setRows: Dispatch<SetStateAction<PurchaseOrderLine[]>>;
  58. setProcessedQty: Dispatch<SetStateAction<number>>;
  59. itemDetail: PurchaseOrderLine;
  60. stockInLine: StockInLine[];
  61. warehouse: WarehouseResult[];
  62. }
  63. export type StockInLineEntryError = {
  64. [field in keyof StockInLine]?: string;
  65. };
  66. export type StockInLineRow = Partial<
  67. StockInLine & {
  68. isActive: boolean | undefined;
  69. _isNew: boolean;
  70. _error: StockInLineEntryError;
  71. } & ResultWithId
  72. >;
  73. class ProcessRowUpdateError extends Error {
  74. public readonly row: StockInLineRow;
  75. public readonly errors: StockInLineEntryError | undefined;
  76. constructor(
  77. row: StockInLineRow,
  78. message?: string,
  79. errors?: StockInLineEntryError
  80. ) {
  81. super(message);
  82. this.row = row;
  83. this.errors = errors;
  84. Object.setPrototypeOf(this, ProcessRowUpdateError.prototype);
  85. }
  86. }
  87. function PoInputGrid({
  88. qc,
  89. setRows,
  90. setProcessedQty,
  91. itemDetail,
  92. stockInLine,
  93. warehouse,
  94. }: Props) {
  95. console.log(itemDetail)
  96. const { t } = useTranslation("home");
  97. const apiRef = useGridApiRef();
  98. const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({});
  99. const getRowId = useCallback<GridRowIdGetter<StockInLineRow>>(
  100. (row) => row.id as number,
  101. []
  102. );
  103. console.log(stockInLine);
  104. const [entries, setEntries] = useState<StockInLineRow[]>(stockInLine || []);
  105. const [modalInfo, setModalInfo] = useState<StockInLine>();
  106. const [qcOpen, setQcOpen] = useState(false);
  107. const [escalOpen, setEscalOpen] = useState(false);
  108. const [stockInOpen, setStockInOpen] = useState(false);
  109. const [putAwayOpen, setPutAwayOpen] = useState(false);
  110. const [currQty, setCurrQty] = useState(() => {
  111. const total = entries.reduce(
  112. (acc, curr) => acc + (curr.acceptedQty || 0),
  113. 0
  114. );
  115. return total;
  116. });
  117. useEffect(() => {
  118. const completedList = entries.filter(
  119. (e) => e.status === "completed"
  120. );
  121. const processedQty = completedList.reduce(
  122. (acc, curr) => acc + (curr.acceptedQty || 0),
  123. 0
  124. );
  125. setProcessedQty(processedQty);
  126. }, [entries]);
  127. const handleDelete = useCallback(
  128. (id: GridRowId) => () => {
  129. setEntries((es) => es.filter((e) => getRowId(e) !== id));
  130. },
  131. [getRowId]
  132. );
  133. const handleStart = useCallback(
  134. (id: GridRowId, params: any) => () => {
  135. setRowModesModel((prev) => ({
  136. ...prev,
  137. [id]: { mode: GridRowModes.View },
  138. }));
  139. setTimeout(async () => {
  140. // post stock in line
  141. console.log("delayed");
  142. console.log(params);
  143. const oldId = params.row.id;
  144. console.log(oldId);
  145. const postData = {
  146. itemId: params.row.itemId,
  147. itemNo: params.row.itemNo,
  148. itemName: params.row.itemName,
  149. purchaseOrderId: params.row.purchaseOrderId,
  150. purchaseOrderLineId: params.row.purchaseOrderLineId,
  151. acceptedQty: params.row.acceptedQty,
  152. };
  153. const res = await createStockInLine(postData);
  154. console.log(res);
  155. setEntries((prev) =>
  156. prev.map((p) => (p.id === oldId ? (res.entity as StockInLine) : p))
  157. );
  158. // do post directly to test
  159. // openStartModal();
  160. }, 200);
  161. },
  162. []
  163. );
  164. const fetchQcDefaultValue = useCallback(async () => {
  165. const authHeader = axiosInstance.defaults.headers['Authorization'];
  166. if (!authHeader) {
  167. return; // Exit the function if the token is not set
  168. }
  169. console.log(authHeader)
  170. const res = await axiosInstance.get<QcItemWithChecks[]>(`${NEXT_PUBLIC_API_URL}/qcResult/${itemDetail.id}`)
  171. console.log(res)
  172. }, [axiosInstance])
  173. const handleQC = useCallback(
  174. (id: GridRowId, params: any) => async () => {
  175. setRowModesModel((prev) => ({
  176. ...prev,
  177. [id]: { mode: GridRowModes.View },
  178. }));
  179. setModalInfo(params.row);
  180. // await fetchQcDefaultValue()
  181. setTimeout(() => {
  182. // open qc modal
  183. console.log("delayed");
  184. openQcModal();
  185. }, 200);
  186. },
  187. []
  188. );
  189. const handleEscalation = useCallback(
  190. (id: GridRowId, params: any) => () => {
  191. setRowModesModel((prev) => ({
  192. ...prev,
  193. [id]: { mode: GridRowModes.View },
  194. }));
  195. setModalInfo(params.row);
  196. setTimeout(() => {
  197. // open qc modal
  198. console.log("delayed");
  199. openEscalationModal();
  200. }, 200);
  201. },
  202. []
  203. );
  204. const handleStockIn = useCallback(
  205. (id: GridRowId, params: any) => () => {
  206. setRowModesModel((prev) => ({
  207. ...prev,
  208. [id]: { mode: GridRowModes.View },
  209. }));
  210. setModalInfo(params.row);
  211. setTimeout(() => {
  212. // open stock in modal
  213. openStockInModal();
  214. // return the record with its status as pending
  215. // update layout
  216. console.log("delayed");
  217. }, 200);
  218. },
  219. []
  220. );
  221. const handlePutAway = useCallback(
  222. (id: GridRowId, params: any) => () => {
  223. setRowModesModel((prev) => ({
  224. ...prev,
  225. [id]: { mode: GridRowModes.View },
  226. }));
  227. setModalInfo(params.row);
  228. setTimeout(() => {
  229. // open stock in modal
  230. openPutAwayModal();
  231. // return the record with its status as pending
  232. // update layout
  233. console.log("delayed");
  234. }, 200);
  235. },
  236. []
  237. );
  238. const printQrcode = useCallback(async (row: any) => {
  239. console.log(row.id)
  240. const postData = {stockInLineIds: [row.id]}
  241. const response = await fetchPoQrcode(postData)
  242. if (response) {
  243. console.log(response)
  244. downloadFile(new Uint8Array(response.blobValue), response.filename!!)
  245. }
  246. }, [fetchPoQrcode, downloadFile])
  247. const handleQrCode = useCallback(
  248. (id: GridRowId, params: any) => () => {
  249. setRowModesModel((prev) => ({
  250. ...prev,
  251. [id]: { mode: GridRowModes.View },
  252. }));
  253. setModalInfo(params.row);
  254. setTimeout(() => {
  255. // open stock in modal
  256. // openPutAwayModal();
  257. // return the record with its status as pending
  258. // update layout
  259. console.log("delayed");
  260. printQrcode(params.row)
  261. }, 200);
  262. },
  263. []
  264. );
  265. const closeQcModal = useCallback(() => {
  266. setQcOpen(false);
  267. }, []);
  268. const openQcModal = useCallback(() => {
  269. setQcOpen(true);
  270. }, []);
  271. const closeStockInModal = useCallback(() => {
  272. setStockInOpen(false);
  273. }, []);
  274. const openStockInModal = useCallback(() => {
  275. setStockInOpen(true);
  276. }, []);
  277. const closePutAwayModal = useCallback(() => {
  278. setPutAwayOpen(false);
  279. }, []);
  280. const openPutAwayModal = useCallback(() => {
  281. setPutAwayOpen(true);
  282. }, []);
  283. const closeEscalationModal = useCallback(() => {
  284. setEscalOpen(false);
  285. }, []);
  286. const openEscalationModal = useCallback(() => {
  287. setEscalOpen(true);
  288. }, []);
  289. const columns = useMemo<GridColDef[]>(
  290. () => [
  291. {
  292. field: "itemNo",
  293. flex: 0.8,
  294. },
  295. {
  296. field: "itemName",
  297. flex: 1,
  298. },
  299. {
  300. field: "acceptedQty",
  301. headerName: "qty",
  302. flex: 0.5,
  303. type: "number",
  304. editable: true,
  305. // replace with tooltip + content
  306. },
  307. {
  308. field: "uom",
  309. headerName: "uom",
  310. flex: 0.5,
  311. renderCell: (params) => {
  312. return params.row.uom.code;
  313. },
  314. },
  315. {
  316. field: "weight",
  317. headerName: "weight",
  318. flex: 0.5,
  319. renderCell: (params) => {
  320. const weight = calculateWeight(params.row.acceptedQty, params.row.uom);
  321. const weightUnit = returnWeightUnit(params.row.uom);
  322. return `${weight} ${weightUnit}`;
  323. },
  324. },
  325. {
  326. field: "status",
  327. flex: 0.5,
  328. // editable: true,
  329. },
  330. {
  331. field: "actions",
  332. type: "actions",
  333. headerName: "start | qc | escalation | stock in | putaway | delete",
  334. flex: 1,
  335. cellClassName: "actions",
  336. getActions: (params) => {
  337. console.log(params.row.status);
  338. const status = params.row.status.toLowerCase();
  339. return [
  340. <GridActionsCellItem
  341. icon={<PlayArrowIcon />}
  342. label="start"
  343. sx={{
  344. color: "primary.main",
  345. // marginRight: 1,
  346. }}
  347. disabled={!(stockInLineStatusMap[status] === 0)}
  348. // set _isNew to false after posting
  349. // or check status
  350. onClick={handleStart(params.row.id, params)}
  351. color="inherit"
  352. key="edit"
  353. />,
  354. <GridActionsCellItem
  355. icon={<FactCheckIcon />}
  356. label="qc"
  357. sx={{
  358. color: "primary.main",
  359. // marginRight: 1,
  360. }}
  361. disabled={
  362. stockInLineStatusMap[status] < 1
  363. }
  364. // set _isNew to false after posting
  365. // or check status
  366. onClick={handleQC(params.row.id, params)}
  367. color="inherit"
  368. key="edit"
  369. />,
  370. <GridActionsCellItem
  371. icon={<NotificationImportantIcon />}
  372. label="escalation"
  373. sx={{
  374. color: "primary.main",
  375. // marginRight: 1,
  376. }}
  377. disabled={
  378. stockInLineStatusMap[status] <= 0 ||
  379. stockInLineStatusMap[status] >= 5
  380. }
  381. // set _isNew to false after posting
  382. // or check status
  383. onClick={handleEscalation(params.row.id, params)}
  384. color="inherit"
  385. key="edit"
  386. />,
  387. <GridActionsCellItem
  388. icon={<ShoppingCartIcon />}
  389. label="stockin"
  390. sx={{
  391. color: "primary.main",
  392. // marginRight: 1,
  393. }}
  394. disabled={stockInLineStatusMap[status] !== 6}
  395. // set _isNew to false after posting
  396. // or check status
  397. onClick={handleStockIn(params.row.id, params)}
  398. color="inherit"
  399. key="edit"
  400. />,
  401. <GridActionsCellItem
  402. icon={<ShoppingCartIcon />}
  403. label="putaway"
  404. sx={{
  405. color: "primary.main",
  406. // marginRight: 1,
  407. }}
  408. disabled={stockInLineStatusMap[status] < 7}
  409. // set _isNew to false after posting
  410. // or check status
  411. onClick={handlePutAway(params.row.id, params)}
  412. color="inherit"
  413. key="edit"
  414. />,
  415. <GridActionsCellItem
  416. icon={<QrCodeIcon />}
  417. label="putaway"
  418. sx={{
  419. color: "primary.main",
  420. // marginRight: 1,
  421. }}
  422. disabled={stockInLineStatusMap[status] !== 8}
  423. // set _isNew to false after posting
  424. // or check status
  425. onClick={handleQrCode(params.row.id, params)}
  426. color="inherit"
  427. key="edit"
  428. />,
  429. <GridActionsCellItem
  430. icon={<DeleteIcon />}
  431. label="Delete"
  432. sx={{
  433. color: "error.main",
  434. }}
  435. disabled={stockInLineStatusMap[status] !== 0}
  436. // disabled={Boolean(params.row.status)}
  437. onClick={handleDelete(params.row.id)}
  438. color="inherit"
  439. key="edit"
  440. />,
  441. ];
  442. },
  443. },
  444. ],
  445. []
  446. );
  447. const addRow = useCallback(() => {
  448. console.log(itemDetail)
  449. const newEntry = {
  450. id: Date.now(),
  451. _isNew: true,
  452. itemId: itemDetail.itemId,
  453. purchaseOrderId: itemDetail.purchaseOrderId,
  454. purchaseOrderLineId: itemDetail.id,
  455. itemNo: itemDetail.itemNo,
  456. itemName: itemDetail.itemName,
  457. acceptedQty: itemDetail.qty - currQty, // this bug
  458. uom: itemDetail.uom,
  459. status: "draft",
  460. };
  461. setEntries((e) => [...e, newEntry]);
  462. setRowModesModel((model) => ({
  463. ...model,
  464. [getRowId(newEntry)]: {
  465. mode: GridRowModes.Edit,
  466. // fieldToFocus: "projectId",
  467. },
  468. }));
  469. }, [currQty, getRowId]);
  470. const validation = useCallback(
  471. (
  472. newRow: GridRowModel<StockInLineRow>
  473. // rowModel: GridRowSelectionModel
  474. ): StockInLineEntryError | undefined => {
  475. const error: StockInLineEntryError = {};
  476. console.log(newRow);
  477. console.log(currQty);
  478. if (newRow.acceptedQty && newRow.acceptedQty > itemDetail.qty) {
  479. error["acceptedQty"] = "qty cannot be greater than remaining qty";
  480. }
  481. return Object.keys(error).length > 0 ? error : undefined;
  482. },
  483. [currQty]
  484. );
  485. const processRowUpdate = useCallback(
  486. (
  487. newRow: GridRowModel<StockInLineRow>,
  488. originalRow: GridRowModel<StockInLineRow>
  489. ) => {
  490. const errors = validation(newRow); // change to validation
  491. if (errors) {
  492. throw new ProcessRowUpdateError(
  493. originalRow,
  494. "validation error",
  495. errors
  496. );
  497. }
  498. const { _isNew, _error, ...updatedRow } = newRow;
  499. const rowToSave = {
  500. ...updatedRow,
  501. } satisfies StockInLineRow;
  502. const newEntries = entries.map((e) =>
  503. getRowId(e) === getRowId(originalRow) ? rowToSave : e
  504. );
  505. setEntries(newEntries);
  506. //update remaining qty
  507. const total = newEntries.reduce(
  508. (acc, curr) => acc + (curr.acceptedQty || 0),
  509. 0
  510. );
  511. setCurrQty(total);
  512. return rowToSave;
  513. },
  514. [getRowId, entries]
  515. );
  516. const onProcessRowUpdateError = useCallback(
  517. (updateError: ProcessRowUpdateError) => {
  518. const errors = updateError.errors;
  519. const oldRow = updateError.row;
  520. apiRef.current.updateRows([{ ...oldRow, _error: errors }]);
  521. },
  522. [apiRef]
  523. );
  524. // useEffect(() => {
  525. // const total = entries.reduce(
  526. // (acc, curr) => acc + (curr.acceptedQty || 0),
  527. // 0
  528. // );
  529. // setDefaultQty(itemDetail.qty - total);
  530. // }, [entries]);
  531. const footer = (
  532. <Box display="flex" gap={2} alignItems="center">
  533. <Button
  534. disableRipple
  535. variant="outlined"
  536. startIcon={<Add />}
  537. disabled={itemDetail.qty - currQty <= 0}
  538. onClick={addRow}
  539. size="small"
  540. >
  541. {t("Record pol")}
  542. </Button>
  543. </Box>
  544. );
  545. return (
  546. <>
  547. <StyledDataGrid
  548. getRowId={getRowId}
  549. apiRef={apiRef}
  550. autoHeight
  551. sx={{
  552. "--DataGrid-overlayHeight": "100px",
  553. ".MuiDataGrid-row .MuiDataGrid-cell.hasError": {
  554. border: "1px solid",
  555. borderColor: "error.main",
  556. },
  557. ".MuiDataGrid-row .MuiDataGrid-cell.hasWarning": {
  558. border: "1px solid",
  559. borderColor: "warning.main",
  560. },
  561. }}
  562. disableColumnMenu
  563. editMode="row"
  564. rows={entries}
  565. rowModesModel={rowModesModel}
  566. onRowModesModelChange={setRowModesModel}
  567. processRowUpdate={processRowUpdate}
  568. onProcessRowUpdateError={onProcessRowUpdateError}
  569. columns={columns}
  570. isCellEditable={(params) => {
  571. const status = params.row.status.toLowerCase();
  572. return (
  573. stockInLineStatusMap[status] >= 0 ||
  574. stockInLineStatusMap[status] <= 1
  575. );
  576. }}
  577. getCellClassName={(params: GridCellParams<StockInLineRow>) => {
  578. let classname = "";
  579. if (params.row._error) {
  580. classname = "hasError";
  581. }
  582. return classname;
  583. }}
  584. slots={{
  585. footer: FooterToolbar,
  586. noRowsOverlay: NoRowsOverlay,
  587. }}
  588. slotProps={{
  589. footer: { child: footer },
  590. }}
  591. />
  592. <>
  593. <PoQcStockInModal
  594. type={"qc"}
  595. setEntries={setEntries}
  596. qc={qc}
  597. open={qcOpen}
  598. onClose={closeQcModal}
  599. itemDetail={modalInfo!!}
  600. />
  601. </>
  602. <>
  603. <PoQcStockInModal
  604. type={"escalation"}
  605. setEntries={setEntries}
  606. // qc={qc}
  607. open={escalOpen}
  608. onClose={closeEscalationModal}
  609. itemDetail={modalInfo!!}
  610. />
  611. </>
  612. <>
  613. <PoQcStockInModal
  614. type={"stockIn"}
  615. setEntries={setEntries}
  616. // qc={qc}
  617. open={stockInOpen}
  618. onClose={closeStockInModal}
  619. itemDetail={modalInfo!!}
  620. />
  621. </>
  622. <>
  623. <PoQcStockInModal
  624. type={"putaway"}
  625. setEntries={setEntries}
  626. open={putAwayOpen}
  627. warehouse={warehouse}
  628. onClose={closePutAwayModal}
  629. itemDetail={modalInfo!!}
  630. />
  631. </>
  632. </>
  633. );
  634. }
  635. const NoRowsOverlay: React.FC = () => {
  636. const { t } = useTranslation("home");
  637. return (
  638. <Box
  639. display="flex"
  640. justifyContent="center"
  641. alignItems="center"
  642. height="100%"
  643. >
  644. <Typography variant="caption">{t("Add some entries!")}</Typography>
  645. </Box>
  646. );
  647. };
  648. const FooterToolbar: React.FC<FooterPropsOverrides> = ({ child }) => {
  649. return <GridToolbarContainer sx={{ p: 2 }}>{child}</GridToolbarContainer>;
  650. };
  651. export default PoInputGrid;