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.
 
 
 

452 rivejä
13 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, Icon, 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 PoQcModal from "./PoQcModal";
  34. import { QcItemWithChecks } from "src/app/api/qc";
  35. import PlayArrowIcon from "@mui/icons-material/PlayArrow";
  36. import { createStockInLine, testFetch } from "@/app/api/po/actions";
  37. import { useSearchParams } from "next/navigation";
  38. import { decimalFormatter, stockInLineStatusMap } from "@/app/utils/formatUtil";
  39. import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline';
  40. interface ResultWithId {
  41. id: number;
  42. }
  43. interface Props {
  44. // qc: QcItemWithChecks[];
  45. // setRows: Dispatch<SetStateAction<PurchaseOrderLine[]>>;
  46. // itemDetail: PurchaseOrderLine;
  47. stockInLine: any[];
  48. }
  49. export type StockInLineEntryError = {
  50. [field in keyof any]?: string;
  51. };
  52. export type StockInLineRow = Partial<
  53. any & {
  54. isActive: boolean | undefined;
  55. _isNew: boolean;
  56. _error: StockInLineEntryError;
  57. } & ResultWithId
  58. >;
  59. class ProcessRowUpdateError extends Error {
  60. public readonly row: StockInLineRow;
  61. public readonly errors: StockInLineEntryError | undefined;
  62. constructor(row: StockInLineRow, message?: string, errors?: StockInLineEntryError) {
  63. super(message);
  64. this.row = row;
  65. this.errors = errors;
  66. Object.setPrototypeOf(this, ProcessRowUpdateError.prototype);
  67. }
  68. }
  69. function TempInputGridForMockUp({ stockInLine }: Props) {
  70. const { t } = useTranslation("schedule");
  71. const apiRef = useGridApiRef();
  72. const [rowModesModel, setRowModesModel] = useState<GridRowModesModel>({});
  73. const getRowId = useCallback<GridRowIdGetter<StockInLineRow>>(
  74. (row) => row.id as number,
  75. []
  76. );
  77. console.log(stockInLine);
  78. const [entries, setEntries] = useState<StockInLineRow[]>(stockInLine || []);
  79. const [modalInfo, setModalInfo] = useState<any>()
  80. const [qcOpen, setQcOpen] = useState(false);
  81. // const [defaultQty, setDefaultQty] = useState(() => {
  82. // const total = entries.reduce((acc, curr) => acc + (curr.acceptedQty || 0), 0);
  83. // return itemDetail.qty - total;
  84. // });
  85. const params = useSearchParams()
  86. const handleDelete = useCallback(
  87. (id: GridRowId) => () => {
  88. setEntries((es) => es.filter((e) => getRowId(e) !== id));
  89. },
  90. [getRowId]
  91. );
  92. const handleStart = useCallback(
  93. (id: GridRowId, params: any) => () => {
  94. setRowModesModel((prev) => ({
  95. ...prev,
  96. [id]: { mode: GridRowModes.View },
  97. }));
  98. setTimeout(async () => {
  99. // post stock in line
  100. console.log("delayed");
  101. console.log(params);
  102. const oldId = params.row.id
  103. console.log(oldId)
  104. const postData = {
  105. itemId: params.row.itemId,
  106. itemNo: params.row.itemNo,
  107. itemName: params.row.itemName,
  108. purchaseOrderId: params.row.purchaseOrderId,
  109. purchaseOrderLineId: params.row.purchaseOrderLineId,
  110. acceptedQty: params.row.acceptedQty,
  111. }
  112. const res = await createStockInLine(postData)
  113. console.log(res)
  114. // setEntries((prev) => prev.map((p) => p.id === oldId ? res.entity : p))
  115. // do post directly to test
  116. // openStartModal();
  117. }, 200);
  118. },
  119. []
  120. );
  121. const handleQC = useCallback(
  122. (id: GridRowId, params: any) => () => {
  123. setRowModesModel((prev) => ({
  124. ...prev,
  125. [id]: { mode: GridRowModes.View },
  126. }));
  127. setModalInfo(params.row)
  128. setTimeout(() => {
  129. // open qc modal
  130. console.log("delayed");
  131. openQcModal();
  132. }, 200);
  133. },
  134. []
  135. );
  136. const handleStockIn = useCallback(
  137. (id: GridRowId) => () => {
  138. setRowModesModel((prev) => ({
  139. ...prev,
  140. [id]: { mode: GridRowModes.View },
  141. }));
  142. setTimeout(() => {
  143. // open stock in modal
  144. // return the record with its status as pending
  145. // update layout
  146. console.log("delayed");
  147. }, 200);
  148. },
  149. []
  150. );
  151. const closeQcModal = useCallback(() => {
  152. setQcOpen(false);
  153. }, []);
  154. const openQcModal = useCallback(() => {
  155. setQcOpen(true);
  156. }, []);
  157. const columns = useMemo<GridColDef[]>(
  158. () => [
  159. {
  160. field: "code",
  161. headerName: t("Code"),
  162. flex: 1,
  163. },
  164. {
  165. field: "name",
  166. headerName: t("Name"),
  167. flex: 1,
  168. },
  169. {
  170. field: "type",
  171. headerName: t("type"),
  172. flex: 1,
  173. },
  174. {
  175. field: "inStockQty",
  176. headerName: t("Available Qty"),
  177. flex: 0.5,
  178. type: "number",
  179. editable: true,
  180. align: "right",
  181. headerAlign: "right",
  182. renderCell: (row) => {
  183. return decimalFormatter.format(row.value)
  184. }
  185. // replace with tooltip + content
  186. },
  187. {
  188. field: "purchaseQty",
  189. headerName: t("Demand Qty"),
  190. flex: 0.5,
  191. editable: true,
  192. align: "right",
  193. headerAlign: "right",
  194. renderCell: (row) => {
  195. return decimalFormatter.format(row.value)
  196. }
  197. },
  198. {
  199. field: "status",
  200. headerName: t("status"),
  201. flex: 0.5,
  202. editable: true,
  203. align: "center",
  204. headerAlign: "center",
  205. renderCell: () => {
  206. return <CheckCircleOutlineIcon
  207. color="success"
  208. fontSize="small"
  209. />
  210. }
  211. },
  212. // {
  213. // field: "actions",
  214. // type: "actions",
  215. // headerName: "start | qc | stock in | delete",
  216. // flex: 1,
  217. // cellClassName: "actions",
  218. // getActions: (params) => {
  219. // // const stockInLineStatusMap: { [status: string]: number } = {
  220. // // draft: 0,
  221. // // pending: 1,
  222. // // qc: 2,
  223. // // determine1: 3,
  224. // // determine2: 4,
  225. // // determine3: 5,
  226. // // receiving: 6,
  227. // // completed: 7,
  228. // // };
  229. // console.log(params.row.status);
  230. // const status = params.row.status.toLowerCase()
  231. // return [
  232. // <GridActionsCellItem
  233. // icon={<PlayArrowIcon />}
  234. // label="start"
  235. // sx={{
  236. // color: "primary.main",
  237. // }}
  238. // disabled={!(stockInLineStatusMap[status] === 0)}
  239. // // set _isNew to false after posting
  240. // // or check status
  241. // onClick={handleStart(params.row.id, params)}
  242. // color="inherit"
  243. // key="edit"
  244. // />,
  245. // <GridActionsCellItem
  246. // icon={<FactCheckIcon />}
  247. // label="qc"
  248. // sx={{
  249. // color: "primary.main",
  250. // }}
  251. // disabled={stockInLineStatusMap[status] <= 0 || stockInLineStatusMap[status] >= 6}
  252. // // set _isNew to false after posting
  253. // // or check status
  254. // onClick={handleQC(params.row.id, params)}
  255. // color="inherit"
  256. // key="edit"
  257. // />,
  258. // <GridActionsCellItem
  259. // icon={<ShoppingCartIcon />}
  260. // label="stockin"
  261. // sx={{
  262. // color: "primary.main",
  263. // }}
  264. // disabled={stockInLineStatusMap[status] !== 6}
  265. // // set _isNew to false after posting
  266. // // or check status
  267. // onClick={handleStockIn(params.row.id)}
  268. // color="inherit"
  269. // key="edit"
  270. // />,
  271. // <GridActionsCellItem
  272. // icon={<DeleteIcon />}
  273. // label="Delete"
  274. // sx={{
  275. // color: "error.main",
  276. // }}
  277. // disabled={stockInLineStatusMap[status] !== 0}
  278. // // disabled={Boolean(params.row.status)}
  279. // onClick={handleDelete(params.row.id)}
  280. // color="inherit"
  281. // key="edit"
  282. // />,
  283. // ];
  284. // },
  285. // },
  286. ],
  287. []
  288. );
  289. // const addRow = useCallback(() => {
  290. // const newEntry = {
  291. // id: Date.now(),
  292. // _isNew: true,
  293. // itemId: itemDetail.itemId,
  294. // purchaseOrderId: itemDetail.purchaseOrderId,
  295. // purchaseOrderLineId: itemDetail.id,
  296. // itemNo: itemDetail.itemNo,
  297. // itemName: itemDetail.itemName,
  298. // acceptedQty: defaultQty,
  299. // status: "draft",
  300. // };
  301. // setEntries((e) => [...e, newEntry]);
  302. // setRowModesModel((model) => ({
  303. // ...model,
  304. // [getRowId(newEntry)]: {
  305. // mode: GridRowModes.Edit,
  306. // // fieldToFocus: "projectId",
  307. // },
  308. // }));
  309. // }, [getRowId]);
  310. const validation = useCallback(
  311. (
  312. newRow: GridRowModel<StockInLineRow>
  313. // rowModel: GridRowSelectionModel
  314. ): StockInLineEntryError | undefined => {
  315. const error: StockInLineEntryError = {};
  316. console.log(newRow);
  317. // if (newRow.acceptedQty && newRow.acceptedQty > defaultQty) {
  318. // error["acceptedQty"] = "qty cannot be greater than remaining qty";
  319. // }
  320. return Object.keys(error).length > 0 ? error : undefined;
  321. },
  322. []
  323. );
  324. const processRowUpdate = useCallback(
  325. (newRow: GridRowModel<StockInLineRow>, originalRow: GridRowModel<StockInLineRow>) => {
  326. const errors = validation(newRow); // change to validation
  327. if (errors) {
  328. throw new ProcessRowUpdateError(
  329. originalRow,
  330. "validation error",
  331. errors
  332. );
  333. }
  334. const { _isNew, _error, ...updatedRow } = newRow;
  335. const rowToSave = {
  336. ...updatedRow,
  337. } satisfies StockInLineRow;
  338. const newEntries = entries.map((e) =>
  339. getRowId(e) === getRowId(originalRow) ? rowToSave : e
  340. );
  341. setEntries(newEntries);
  342. //update remaining qty
  343. const total = newEntries.reduce((acc, curr) => acc + (curr.acceptedQty || 0), 0);
  344. // setDefaultQty(itemDetail.qty - total);
  345. return rowToSave;
  346. },
  347. [getRowId, entries]
  348. );
  349. const onProcessRowUpdateError = useCallback(
  350. (updateError: ProcessRowUpdateError) => {
  351. const errors = updateError.errors;
  352. const oldRow = updateError.row;
  353. apiRef.current.updateRows([{ ...oldRow, _error: errors }]);
  354. },
  355. [apiRef]
  356. );
  357. // useEffect(() => {
  358. // const total = entries.reduce((acc, curr) => acc + (curr.acceptedQty || 0), 0);
  359. // setDefaultQty(itemDetail.qty - total);
  360. // }, [entries]);
  361. // const footer = (
  362. // <Box display="flex" gap={2} alignItems="center">
  363. // <Button
  364. // disableRipple
  365. // variant="outlined"
  366. // startIcon={<Add />}
  367. // disabled={defaultQty <= 0}
  368. // onClick={addRow}
  369. // size="small"
  370. // >
  371. // {t("Record pol")}
  372. // </Button>
  373. // </Box>
  374. // );
  375. return (
  376. <>
  377. <StyledDataGrid
  378. getRowId={getRowId}
  379. apiRef={apiRef}
  380. autoHeight
  381. sx={{
  382. "--DataGrid-overlayHeight": "100px",
  383. ".MuiDataGrid-row .MuiDataGrid-cell.hasError": {
  384. border: "1px solid",
  385. borderColor: "error.main",
  386. },
  387. ".MuiDataGrid-row .MuiDataGrid-cell.hasWarning": {
  388. border: "1px solid",
  389. borderColor: "warning.main",
  390. },
  391. }}
  392. disableColumnMenu
  393. editMode="row"
  394. rows={entries}
  395. rowModesModel={rowModesModel}
  396. onRowModesModelChange={setRowModesModel}
  397. processRowUpdate={processRowUpdate}
  398. onProcessRowUpdateError={onProcessRowUpdateError}
  399. columns={columns}
  400. getCellClassName={(params: GridCellParams<StockInLineRow>) => {
  401. let classname = "";
  402. if (params.row._error) {
  403. classname = "hasError";
  404. }
  405. return classname;
  406. }}
  407. // slots={{
  408. // footer: FooterToolbar,
  409. // noRowsOverlay: NoRowsOverlay,
  410. // }}
  411. // slotProps={{
  412. // footer: { child: footer },
  413. // }}
  414. />
  415. </>
  416. );
  417. }
  418. const NoRowsOverlay: React.FC = () => {
  419. const { t } = useTranslation("home");
  420. return (
  421. <Box
  422. display="flex"
  423. justifyContent="center"
  424. alignItems="center"
  425. height="100%"
  426. >
  427. <Typography variant="caption">{t("Add some entries!")}</Typography>
  428. </Box>
  429. );
  430. };
  431. const FooterToolbar: React.FC<FooterPropsOverrides> = ({ child }) => {
  432. return <GridToolbarContainer sx={{ p: 2 }}>{child}</GridToolbarContainer>;
  433. };
  434. export default TempInputGridForMockUp;