"use client"; import { FooterPropsOverrides, GridActionsCellItem, GridCellParams, GridRowId, GridRowIdGetter, GridRowModel, GridRowModes, GridRowModesModel, GridToolbarContainer, useGridApiRef, } from "@mui/x-data-grid"; import { Dispatch, MutableRefObject, SetStateAction, useCallback, useEffect, useMemo, useState, } from "react"; import StyledDataGrid from "../StyledDataGrid"; import { GridColDef } from "@mui/x-data-grid"; import { Box, Button, Grid, Typography } from "@mui/material"; import { useTranslation } from "react-i18next"; import { Add } from "@mui/icons-material"; import SaveIcon from "@mui/icons-material/Save"; import DeleteIcon from "@mui/icons-material/Delete"; import CancelIcon from "@mui/icons-material/Cancel"; import FactCheckIcon from "@mui/icons-material/FactCheck"; import ShoppingCartIcon from "@mui/icons-material/ShoppingCart"; import { QcItemWithChecks } from "src/app/api/qc"; import PlayArrowIcon from "@mui/icons-material/PlayArrow"; import { PurchaseOrderLine, StockInLine } from "@/app/api/po"; import { createStockInLine, PurchaseQcResult } from "@/app/api/po/actions"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; import { returnWeightUnit, calculateWeight, stockInLineStatusMap, } from "@/app/utils/formatUtil"; // import PoQcStockInModal from "./PoQcStockInModal"; import NotificationImportantIcon from "@mui/icons-material/NotificationImportant"; import { WarehouseResult } from "@/app/api/warehouse"; import LooksOneIcon from "@mui/icons-material/LooksOne"; import LooksTwoIcon from "@mui/icons-material/LooksTwo"; import Looks3Icon from "@mui/icons-material/Looks3"; import axiosInstance from "@/app/(main)/axios/axiosInstance"; // import axios, { AxiosRequestConfig } from "axios"; import { BASE_API_URL, NEXT_PUBLIC_API_URL } from "@/config/api"; import qs from "qs"; import QrCodeIcon from "@mui/icons-material/QrCode"; import { downloadFile } from "@/app/utils/commonUtil"; import { fetchPoQrcode } from "@/app/api/pdf/actions"; import { fetchQcResult } from "@/app/api/qc/actions"; import PoQcStockInModal from "./PoQcStockInModal"; import DoDisturbIcon from "@mui/icons-material/DoDisturb"; import { useSession } from "next-auth/react"; import PoQcStockInModalVer2 from "./QcStockInModalVer2"; import { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil"; interface ResultWithId { id: number; } interface Props { qc: QcItemWithChecks[]; setRows: Dispatch>; setStockInLine: Dispatch>; setProcessedQty: Dispatch>; itemDetail: PurchaseOrderLine; stockInLine: StockInLine[]; warehouse: WarehouseResult[]; } export type StockInLineEntryError = { [field in keyof StockInLine]?: string; }; export type StockInLineRow = Partial< StockInLine & { isActive: boolean | undefined; _isNew: boolean; _error: StockInLineEntryError; } & ResultWithId >; class ProcessRowUpdateError extends Error { public readonly row: StockInLineRow; public readonly errors: StockInLineEntryError | undefined; constructor( row: StockInLineRow, message?: string, errors?: StockInLineEntryError, ) { super(message); this.row = row; this.errors = errors; Object.setPrototypeOf(this, ProcessRowUpdateError.prototype); } } function PoInputGrid({ qc, setRows, setStockInLine, setProcessedQty, itemDetail, stockInLine, warehouse, }: Props) { console.log(itemDetail); const { t } = useTranslation("purchaseOrder"); const apiRef = useGridApiRef(); const [rowModesModel, setRowModesModel] = useState({}); const getRowId = useCallback>( (row) => row.id as number, [], ); console.log(stockInLine); const [entries, setEntries] = useState(stockInLine || []); const [modalInfo, setModalInfo] = useState< StockInLine & { qcResult?: PurchaseQcResult[] } >(); const pathname = usePathname() const router = useRouter(); const searchParams = useSearchParams(); const [qcOpen, setQcOpen] = useState(false); const [escalOpen, setEscalOpen] = useState(false); const [stockInOpen, setStockInOpen] = useState(false); const [putAwayOpen, setPutAwayOpen] = useState(false); const [rejectOpen, setRejectOpen] = useState(false); const [btnIsLoading, setBtnIsLoading] = useState(false); const [currQty, setCurrQty] = useState(() => { const total = entries.reduce( (acc, curr) => acc + (curr.acceptedQty || 0), 0, ); return total; }); const { data: session } = useSession(); useEffect(() => { const completedList = entries.filter( (e) => stockInLineStatusMap[e.status!] >= 8, ); const processedQty = completedList.reduce( (acc, curr) => acc + (curr.acceptedQty || 0), 0, ); setProcessedQty(processedQty); }, [entries, setProcessedQty]); const handleDelete = useCallback( (id: GridRowId) => () => { setEntries((es) => es.filter((e) => getRowId(e) !== id)); }, [getRowId], ); const closeQcModal = useCallback(() => { setQcOpen(false); }, []); const openQcModal = useCallback(() => { setQcOpen(true); }, []); const closeStockInModal = useCallback(() => { setStockInOpen(false); }, []); const openStockInModal = useCallback(() => { setStockInOpen(true); }, []); const closePutAwayModal = useCallback(() => { setPutAwayOpen(false); }, []); const openPutAwayModal = useCallback(() => { setPutAwayOpen(true); }, []); const closeEscalationModal = useCallback(() => { setEscalOpen(false); }, []); const openEscalationModal = useCallback(() => { setEscalOpen(true); }, []); const closeRejectModal = useCallback(() => { setRejectOpen(false); }, []); const openRejectModal = useCallback(() => { setRejectOpen(true); }, []); const handleStart = useCallback( (id: GridRowId, params: any) => () => { setBtnIsLoading(true); setRowModesModel((prev) => ({ ...prev, [id]: { mode: GridRowModes.View }, })); setTimeout(async () => { // post stock in line const oldId = params.row.id; const postData = { itemId: params.row.itemId, itemNo: params.row.itemNo, itemName: params.row.itemName, purchaseOrderId: params.row.purchaseOrderId, purchaseOrderLineId: params.row.purchaseOrderLineId, acceptedQty: params.row.acceptedQty, }; const res = await createStockInLine(postData); console.log(res); setEntries((prev) => prev.map((p) => (p.id === oldId ? (res.entity as StockInLine) : p)), ); setStockInLine( (prev) => prev.map((p) => p.id === oldId ? (res.entity as StockInLine) : p, ) as StockInLine[], ); setBtnIsLoading(false); // do post directly to test // openStartModal(); }, 200); }, [setStockInLine], ); const fetchQcDefaultValue = useCallback(async (stockInLineId: GridRowId) => { return await fetchQcResult(stockInLineId as number); }, []); const handleQC = useCallback( (id: GridRowId, params: any) => async () => { setBtnIsLoading(true); setRowModesModel((prev) => ({ ...prev, [id]: { mode: GridRowModes.View }, })); const qcResult = await fetchQcDefaultValue(id); console.log(params.row); console.log(qcResult); setModalInfo({ ...params.row, qcResult: qcResult, }); // set default values setTimeout(() => { // open qc modal console.log("delayed"); openQcModal(); setBtnIsLoading(false); }, 200); }, [fetchQcDefaultValue, openQcModal], ); const [newOpen, setNewOpen] = useState(false); const stockInLineId = searchParams.get("stockInLineId"); const closeNewModal = useCallback(() => { const newParams = new URLSearchParams(searchParams.toString()); newParams.delete("stockInLineId"); // Remove the parameter router.replace(`${pathname}?${newParams.toString()}`); setTimeout(() => { setNewOpen(false); // Close the modal first }, 300); // Add a delay to avoid immediate re-trigger of useEffect }, [searchParams, pathname, router]); // Open modal const openNewModal = useCallback(() => { setNewOpen(true); }, []); // Button handler to update the URL and open the modal const handleNewQC = useCallback( (id: GridRowId, params: any) => async () => { console.log(id) console.log(params) setBtnIsLoading(true); setRowModesModel((prev) => ({ ...prev, [id]: { mode: GridRowModes.View }, })); const qcResult = await fetchQcDefaultValue(id); setModalInfo({ ...params.row, qcResult: qcResult, }); setTimeout(() => { const newParams = new URLSearchParams(searchParams.toString()); newParams.set("stockInLineId", id.toString()); // Ensure `set` to avoid duplicates router.replace(`${pathname}?${newParams.toString()}`); console.log("hello") openNewModal() setBtnIsLoading(false); }, 200); }, [fetchQcDefaultValue, openNewModal, pathname, router, searchParams] ); // Open modal if `stockInLineId` exists in the URL useEffect(() => { if (stockInLineId) { console.log("heeloo") console.log(stockInLineId) handleNewQC(stockInLineId, apiRef.current.getRow(stockInLineId)); } }, [stockInLineId, newOpen, handleNewQC, apiRef]); const handleEscalation = useCallback( (id: GridRowId, params: any) => () => { // setBtnIsLoading(true); setRowModesModel((prev) => ({ ...prev, [id]: { mode: GridRowModes.View }, })); setModalInfo(params.row); setTimeout(() => { // open qc modal console.log("delayed"); openEscalationModal(); // setBtnIsLoading(false); }, 200); }, [openEscalationModal], ); const handleReject = useCallback( (id: GridRowId, params: any) => () => { setRowModesModel((prev) => ({ ...prev, [id]: { mode: GridRowModes.View }, })); setModalInfo(params.row); setTimeout(() => { // open stock in modal // openPutAwayModal(); // return the record with its status as pending // update layout console.log("delayed"); openRejectModal(); // printQrcode(params.row); }, 200); }, [openRejectModal], ); const handleStockIn = useCallback( (id: GridRowId, params: any) => () => { // setBtnIsLoading(true); setRowModesModel((prev) => ({ ...prev, [id]: { mode: GridRowModes.View }, })); setModalInfo(params.row); setTimeout(() => { // open stock in modal openStockInModal(); // return the record with its status as pending // update layout console.log("delayed"); // setBtnIsLoading(false); }, 200); }, [openStockInModal], ); const handlePutAway = useCallback( (id: GridRowId, params: any) => () => { // setBtnIsLoading(true); setRowModesModel((prev) => ({ ...prev, [id]: { mode: GridRowModes.View }, })); setModalInfo(params.row); setTimeout(() => { // open stock in modal openPutAwayModal(); // return the record with its status as pending // update layout console.log("delayed"); // setBtnIsLoading(false); }, 200); }, [openPutAwayModal], ); const printQrcode = useCallback( async (row: any) => { setBtnIsLoading(true); console.log(row.id); const postData = { stockInLineIds: [row.id] }; // const postData = { stockInLineIds: [42,43,44] }; const response = await fetchPoQrcode(postData); if (response) { console.log(response); downloadFile(new Uint8Array(response.blobValue), response.filename!); } setBtnIsLoading(false); }, [], ); // const handleQrCode = useCallback( // (id: GridRowId, params: any) => () => { // setRowModesModel((prev) => ({ // ...prev, // [id]: { mode: GridRowModes.View }, // })); // setModalInfo(params.row); // setTimeout(() => { // // open stock in modal // // openPutAwayModal(); // // return the record with its status as pending // // update layout // console.log("delayed"); // printQrcode(params.row); // }, 200); // }, // [printQrcode], // ); const columns = useMemo( () => [ // { // field: "itemNo", // headerName: t("itemNo"), // width: 100, // // flex: 0.4, // }, { field: "dnNo", headerName: t("dnNo"), width: 125, renderCell: () => { return <>DN0000001 } // flex: 0.4, }, { field: "dnDate", headerName: t("dnDate"), width: 125, renderCell: () => { return <>07/08/2025 } // flex: 0.4, }, // { // field: "itemName", // headerName: t("itemName"), // width: 100, // // flex: 0.6, // }, { field: "acceptedQty", headerName: t("acceptedQty"), // flex: 0.5, width: 125, type: "number", // editable: true, // replace with tooltip + content renderCell: (params) => { return integerFormatter.format(params.value) } }, { field: "uom", headerName: t("uom"), width: 120, // flex: 0.5, renderCell: (params) => { return params.row.uom.code; }, }, { field: "weight", headerName: t("weight"), width: 120, // flex: 0.5, renderCell: (params) => { const weight = calculateWeight( params.row.acceptedQty, params.row.uom, ); const weightUnit = returnWeightUnit(params.row.uom); return `${decimalFormatter.format(weight)} ${weightUnit}`; }, }, { field: "status", headerName: t("status"), width: 70, // flex: 0.5, renderCell: (params) => { return t(`${params.row.status}`); }, }, { field: "actions", type: "actions", // headerName: `${t("start")} | ${t("qc")} | ${t("escalation")} | ${t( // "stock in", // )} | ${t("putaway")} | ${t("delete")}`, headerName: "動作", // headerName: "start | qc | escalation | stock in | putaway | delete", width: 300, // flex: 2, cellClassName: "actions", getActions: (params) => { // console.log(params.row.status); const status = params.row.status.toLowerCase(); // console.log(stockInLineStatusMap[status]); // console.log(session?.user?.abilities?.includes("APPROVAL")); return [ {t("qc processing")}} label="start" sx={{ color: "primary.main", // marginRight: 1, }} // disabled={!(stockInLineStatusMap[status] === 0)} // set _isNew to false after posting // or check status onClick={handleNewQC(params.row.id, params)} color="inherit" key="edit" />, {t("putawayBtn")}} label="start" sx={{ color: "primary.main", // marginRight: 1, }} // disabled={!(stockInLineStatusMap[status] === 0)} // set _isNew to false after posting // or check status onClick={handleStart(params.row.id, params)} color="inherit" key="edit" />, // {t("qc processing")}} // label="start" // sx={{ // color: "primary.main", // // marginRight: 1, // }} // disabled={!(stockInLineStatusMap[status] === 0)} // // set _isNew to false after posting // // or check status // onClick={handleStart(params.row.id, params)} // color="inherit" // key="edit" // />, // } // label="qc" // sx={{ // color: "primary.main", // // marginRight: 1, // }} // disabled={ // // stockInLineStatusMap[status] === 9 || // stockInLineStatusMap[status] < 1 // } // // set _isNew to false after posting // // or check status // onClick={handleQC(params.row.id, params)} // color="inherit" // key="edit" // />, // } // label="escalation" // sx={{ // color: "primary.main", // // marginRight: 1, // }} // disabled={ // stockInLineStatusMap[status] === 9 || // stockInLineStatusMap[status] <= 0 || // stockInLineStatusMap[status] >= 5 // } // // set _isNew to false after posting // // or check status // onClick={handleEscalation(params.row.id, params)} // color="inherit" // key="edit" // />, // } // label="stockin" // sx={{ // color: "primary.main", // // marginRight: 1, // }} // disabled={ // stockInLineStatusMap[status] === 9 || // stockInLineStatusMap[status] <= 2 || // stockInLineStatusMap[status] >= 7 || // (stockInLineStatusMap[status] >= 3 && // stockInLineStatusMap[status] <= 5 && // !session?.user?.abilities?.includes("APPROVAL")) // } // // set _isNew to false after posting // // or check status // onClick={handleStockIn(params.row.id, params)} // color="inherit" // key="edit" // />, // } // label="putaway" // sx={{ // color: "primary.main", // // marginRight: 1, // }} // disabled={ // stockInLineStatusMap[status] === 9 || // stockInLineStatusMap[status] < 7 // } // // set _isNew to false after posting // // or check status // onClick={handlePutAway(params.row.id, params)} // color="inherit" // key="edit" // />, // // } // // label="putaway" // // sx={{ // // color: "primary.main", // // // marginRight: 1, // // }} // // disabled={stockInLineStatusMap[status] === 9 || stockInLineStatusMap[status] !== 8} // // // set _isNew to false after posting // // // or check status // // onClick={handleQrCode(params.row.id, params)} // // color="inherit" // // key="edit" // // />, // = 1 ? ( // // ) : ( // // ) // } // label="Delete" // sx={{ // color: "error.main", // }} // disabled={ // stockInLineStatusMap[status] >= 7 && // stockInLineStatusMap[status] <= 9 // } // onClick={ // stockInLineStatusMap[status] === 0 // ? handleDelete(params.row.id) // : handleReject(params.row.id, params) // } // color="inherit" // key="edit" // />, ]; }, }, ], [t, handleStart, handleQC, handleEscalation, session?.user?.abilities, handleStockIn, handlePutAway, handleDelete, handleReject], ); const addRow = useCallback(() => { console.log(itemDetail); const newEntry = { id: Date.now(), _isNew: true, itemId: itemDetail.itemId, purchaseOrderId: itemDetail.purchaseOrderId, purchaseOrderLineId: itemDetail.id, itemNo: itemDetail.itemNo, itemName: itemDetail.itemName, acceptedQty: itemDetail.qty - currQty, // this bug uom: itemDetail.uom, status: "draft", }; setEntries((e) => [...e, newEntry]); setRowModesModel((model) => ({ ...model, [getRowId(newEntry)]: { mode: GridRowModes.Edit, // fieldToFocus: "projectId", }, })); }, [currQty, getRowId, itemDetail]); const validation = useCallback( ( newRow: GridRowModel, // rowModel: GridRowSelectionModel ): StockInLineEntryError | undefined => { const error: StockInLineEntryError = {}; console.log(newRow); console.log(currQty); if (newRow.acceptedQty && newRow.acceptedQty > itemDetail.qty) { error["acceptedQty"] = t("qty cannot be greater than remaining qty"); } return Object.keys(error).length > 0 ? error : undefined; }, [currQty, itemDetail.qty, t], ); const processRowUpdate = useCallback( ( newRow: GridRowModel, originalRow: GridRowModel, ) => { const errors = validation(newRow); // change to validation if (errors) { throw new ProcessRowUpdateError( originalRow, "validation error", errors, ); } const { _isNew, _error, ...updatedRow } = newRow; const rowToSave = { ...updatedRow, } satisfies StockInLineRow; const newEntries = entries.map((e) => getRowId(e) === getRowId(originalRow) ? rowToSave : e, ); setStockInLine(newEntries as StockInLine[]); console.log("triggered"); setEntries(newEntries); //update remaining qty const total = newEntries.reduce( (acc, curr) => acc + (curr.acceptedQty || 0), 0, ); setCurrQty(total); return rowToSave; }, [validation, entries, setStockInLine, getRowId], ); const onProcessRowUpdateError = useCallback( (updateError: ProcessRowUpdateError) => { const errors = updateError.errors; const oldRow = updateError.row; apiRef.current.updateRows([{ ...oldRow, _error: errors }]); }, [apiRef], ); const footer = ( <> {/* */} ); return ( <> { const status = params.row.status.toLowerCase(); return ( stockInLineStatusMap[status] >= 0 || stockInLineStatusMap[status] <= 1 ); }} getCellClassName={(params: GridCellParams) => { let classname = ""; if (params.row._error) { classname = "hasError"; } return classname; }} slots={{ footer: FooterToolbar, noRowsOverlay: NoRowsOverlay, }} slotProps={{ footer: { child: footer }, }} /> {modalInfo !== undefined && ( <> ) } {modalInfo !== undefined && ( <> )} {modalInfo !== undefined && ( <> )} {modalInfo !== undefined && ( <> )} {modalInfo !== undefined && ( <> )} {modalInfo !== undefined && ( <> )} ); } const NoRowsOverlay: React.FC = () => { const { t } = useTranslation("home"); return ( {t("Add some entries!")} ); }; const FooterToolbar: React.FC = ({ child }) => { return {child}; }; export default PoInputGrid;