| @@ -26,3 +26,10 @@ export const convertObjToURLSearchParams = <T extends object>( | |||
| return params.toString(); | |||
| }; | |||
| export const getCustomWidth = (): number => { | |||
| const LIMIT_WIDTH = 1000 | |||
| const CUSTOM_WIDTH = 1100 | |||
| if (window.innerWidth < LIMIT_WIDTH) return CUSTOM_WIDTH | |||
| return window.innerWidth | |||
| } | |||
| @@ -37,7 +37,9 @@ import { | |||
| GridApiCommunity, | |||
| GridSlotsComponentsProps, | |||
| } from "@mui/x-data-grid/internals"; | |||
| // T == CreatexxxInputs map of the form's fields | |||
| // V == target field input inside CreatexxxInputs, e.g. qcChecks: ItemQc[], V = ItemQc | |||
| // E == error | |||
| interface ResultWithId { | |||
| id: string | number; | |||
| } | |||
| @@ -97,7 +99,7 @@ export class ProcessRowUpdateError<T, E> extends Error { | |||
| Object.setPrototypeOf(this, ProcessRowUpdateError.prototype); | |||
| } | |||
| } | |||
| // T == CreatexxxInputs | |||
| // T == CreatexxxInputs map of the form's fields | |||
| // V == target field input inside CreatexxxInputs, e.g. qcChecks: ItemQc[], V = ItemQc | |||
| // E == error | |||
| function InputDataGrid<T, V, E>({ | |||
| @@ -126,7 +128,11 @@ function InputDataGrid<T, V, E>({ | |||
| const list: TableRow<V, E>[] = getValues(formKey); | |||
| return list && list.length > 0 ? list : []; | |||
| }); | |||
| const originalRows = list && list.length > 0 ? list : []; | |||
| // const originalRows = list && list.length > 0 ? list : []; | |||
| const originalRows = useMemo(() => ( | |||
| list && list.length > 0 ? list : [] | |||
| ), [list]) | |||
| // const originalRowModel = originalRows.filter((li) => li.isActive).map(i => i.id) as GridRowSelectionModel | |||
| const [rowSelectionModel, setRowSelectionModel] = | |||
| useState<GridRowSelectionModel>(() => { | |||
| @@ -154,7 +160,7 @@ function InputDataGrid<T, V, E>({ | |||
| console.log(errors); | |||
| apiRef.current.updateRows([{ ...row, _error: errors }]); | |||
| }, | |||
| [apiRef, rowModesModel], | |||
| [apiRef], | |||
| ); | |||
| const processRowUpdate = useCallback( | |||
| @@ -202,7 +208,7 @@ function InputDataGrid<T, V, E>({ | |||
| const reset = useCallback(() => { | |||
| setRowModesModel({}); | |||
| setRows(originalRows); | |||
| }, []); | |||
| }, [originalRows]); | |||
| const handleCancel = useCallback( | |||
| (id: GridRowId) => () => { | |||
| @@ -219,14 +225,14 @@ function InputDataGrid<T, V, E>({ | |||
| ); | |||
| } | |||
| }, | |||
| [setRowModesModel, rows], | |||
| [rows, getRowId], | |||
| ); | |||
| const handleDelete = useCallback( | |||
| (id: GridRowId) => () => { | |||
| setRows((prevRows) => prevRows.filter((row) => getRowId(row) !== id)); | |||
| }, | |||
| [], | |||
| [getRowId], | |||
| ); | |||
| const _columns = useMemo<GridColDef[]>( | |||
| @@ -281,7 +287,7 @@ function InputDataGrid<T, V, E>({ | |||
| // console.log(formKey) | |||
| // console.log(rows) | |||
| setValue(formKey, rows); | |||
| }, [formKey, rows]); | |||
| }, [formKey, rows, setValue]); | |||
| const footer = ( | |||
| <Box display="flex" gap={2} alignItems="center"> | |||
| @@ -64,7 +64,8 @@ const style = { | |||
| pt: 5, | |||
| px: 5, | |||
| pb: 10, | |||
| width: 1500, | |||
| // width: 1500, | |||
| width: { xs: "100%", sm: "100%", md: "100%" }, | |||
| }; | |||
| interface DisableButton { | |||
| releaseBtn: boolean; | |||
| @@ -107,6 +107,7 @@ const CreateForm: React.FC<Props> = ({ items }) => { | |||
| { | |||
| field: "itemId", | |||
| headerName: t("Item"), | |||
| // width: 100, | |||
| flex: 1, | |||
| editable: true, | |||
| valueFormatter(params) { | |||
| @@ -162,6 +163,7 @@ const CreateForm: React.FC<Props> = ({ items }) => { | |||
| { | |||
| field: "qty", | |||
| headerName: t("qty"), | |||
| // width: 100, | |||
| flex: 1, | |||
| type: "number", | |||
| editable: true, | |||
| @@ -181,6 +183,7 @@ const CreateForm: React.FC<Props> = ({ items }) => { | |||
| { | |||
| field: "uom", | |||
| headerName: t("uom"), | |||
| // width: 100, | |||
| flex: 1, | |||
| editable: true, | |||
| // renderEditCell(params: GridRenderEditCellParams<PolRow>) { | |||
| @@ -257,42 +260,42 @@ const CreateForm: React.FC<Props> = ({ items }) => { | |||
| </FormControl> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <Controller | |||
| control={control} | |||
| name="targetDate" | |||
| // rules={{ required: !Boolean(productionDate) }} | |||
| render={({ field }) => { | |||
| return ( | |||
| <LocalizationProvider | |||
| dateAdapter={AdapterDayjs} | |||
| adapterLocale={`${language}-hk`} | |||
| > | |||
| <DatePicker | |||
| {...field} | |||
| sx={{ width: "100%" }} | |||
| label={t("targetDate")} | |||
| value={targetDate ? dayjs(targetDate) : undefined} | |||
| onChange={(date) => { | |||
| console.log(date); | |||
| if (!date) return; | |||
| console.log(date.format(INPUT_DATE_FORMAT)); | |||
| setValue("targetDate", date.format(INPUT_DATE_FORMAT)); | |||
| // field.onChange(date); | |||
| }} | |||
| inputRef={field.ref} | |||
| slotProps={{ | |||
| textField: { | |||
| // required: true, | |||
| error: Boolean(errors.targetDate?.message), | |||
| helperText: errors.targetDate?.message, | |||
| }, | |||
| }} | |||
| /> | |||
| </LocalizationProvider> | |||
| ); | |||
| }} | |||
| /> | |||
| </Grid> | |||
| <Controller | |||
| control={control} | |||
| name="targetDate" | |||
| // rules={{ required: !Boolean(productionDate) }} | |||
| render={({ field }) => { | |||
| return ( | |||
| <LocalizationProvider | |||
| dateAdapter={AdapterDayjs} | |||
| adapterLocale={`${language}-hk`} | |||
| > | |||
| <DatePicker | |||
| {...field} | |||
| sx={{ width: "100%" }} | |||
| label={t("targetDate")} | |||
| value={targetDate ? dayjs(targetDate) : undefined} | |||
| onChange={(date) => { | |||
| console.log(date); | |||
| if (!date) return; | |||
| console.log(date.format(INPUT_DATE_FORMAT)); | |||
| setValue("targetDate", date.format(INPUT_DATE_FORMAT)); | |||
| // field.onChange(date); | |||
| }} | |||
| inputRef={field.ref} | |||
| slotProps={{ | |||
| textField: { | |||
| // required: true, | |||
| error: Boolean(errors.targetDate?.message), | |||
| helperText: errors.targetDate?.message, | |||
| }, | |||
| }} | |||
| /> | |||
| </LocalizationProvider> | |||
| ); | |||
| }} | |||
| /> | |||
| </Grid> | |||
| </Grid> | |||
| <Grid | |||
| container | |||
| @@ -21,7 +21,7 @@ const style = { | |||
| px: 5, | |||
| pb: 10, | |||
| display: "block", | |||
| width: { xs: "60%", sm: "60%", md: "60%" }, | |||
| width: { xs: "100%", sm: "100%", md: "100%" }, | |||
| }; | |||
| interface Props extends Omit<ModalProps, "children"> { | |||
| @@ -62,7 +62,7 @@ const CreatePickOrderModal: React.FC<Props> = ({ | |||
| return ( | |||
| <> | |||
| <FormProvider {...formProps}> | |||
| <Modal open={open} onClose={closeHandler} sx={{ overflowY: "scroll" }}> | |||
| <Modal open={open} onClose={closeHandler}> | |||
| <Box | |||
| sx={style} | |||
| component="form" | |||
| @@ -41,41 +41,27 @@ import { | |||
| fetchStockInLineInfo, | |||
| PurchaseQcResult, | |||
| startPo, | |||
| testFetch, | |||
| } from "@/app/api/po/actions"; | |||
| import { | |||
| use, | |||
| useCallback, | |||
| useContext, | |||
| useEffect, | |||
| useMemo, | |||
| useState, | |||
| } from "react"; | |||
| import { FormProvider, useForm } from "react-hook-form"; | |||
| import KeyboardArrowDownIcon from "@mui/icons-material/KeyboardArrowDown"; | |||
| import KeyboardArrowUpIcon from "@mui/icons-material/KeyboardArrowUp"; | |||
| import InputDataGrid, { | |||
| TableRow as InputTableRow, | |||
| } from "../InputDataGrid/InputDataGrid"; | |||
| import PoInputGrid from "./PoInputGrid"; | |||
| import { QcItemWithChecks } from "@/app/api/qc"; | |||
| import { useSearchParams } from "next/navigation"; | |||
| import { WarehouseResult } from "@/app/api/warehouse"; | |||
| import { calculateWeight, returnWeightUnit } from "@/app/utils/formatUtil"; | |||
| import QrCodeScanner from "../QrCodeScanner"; | |||
| import { CameraDevice, Html5Qrcode } from "html5-qrcode"; | |||
| import { CameraContext } from "../Cameras/CameraProvider"; | |||
| import StyledDataGrid from "../StyledDataGrid"; | |||
| import { QrCodeInfo } from "@/app/api/qrcode"; | |||
| import { fetchQcResult } from "@/app/api/qc/actions"; | |||
| import PoQcStockInModal from "./PoQcStockInModal"; | |||
| import ReactQrCodeScannerModal, { | |||
| ScannerConfig, | |||
| } from "../ReactQrCodeScanner/ReactQrCodeScanner"; | |||
| import QrModal from "./QrModal"; | |||
| import { PlayArrow } from "@mui/icons-material"; | |||
| import DoneIcon from "@mui/icons-material/Done"; | |||
| import { QrCode } from "../QrCode"; | |||
| import { getCustomWidth } from "@/app/utils/commonUtil"; | |||
| type Props = { | |||
| po: PoResult; | |||
| @@ -272,8 +258,9 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
| }; | |||
| // break; | |||
| } | |||
| }, [purchaseOrder, handleStartPo, handleCompletePo]); | |||
| }, [purchaseOrder.status, t, handleStartPo, handleCompletePo]); | |||
| console.log(window.innerWidth) | |||
| return ( | |||
| <> | |||
| <Stack | |||
| @@ -301,20 +288,7 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
| {buttonData.buttonText} | |||
| </Button> | |||
| </Grid> | |||
| {/* {purchaseOrder.status.toLowerCase() === "pending" && ( | |||
| <Grid item> | |||
| <Button onClick={handleStartPo}>Start</Button> | |||
| </Grid> | |||
| )} | |||
| {purchaseOrder.status.toLowerCase() === "receiving" && ( | |||
| <Grid item> | |||
| <Button onClick={handleCompletePo}>Complete</Button> | |||
| </Grid> | |||
| )} */} | |||
| </Grid> | |||
| {/* <Grid container xs={12} justifyContent="space-between"> | |||
| <Button onClick={handleCompletePo}>Complete</Button> | |||
| </Grid> */} | |||
| <Grid container xs={12} justifyContent="space-between"> | |||
| <Grid item xs={8}> | |||
| <Tabs | |||
| @@ -346,7 +320,8 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||
| </Grid> | |||
| {/* tab 1 */} | |||
| <Grid sx={{ display: tabIndex === 0 ? "block" : "none" }}> | |||
| <TableContainer component={Paper}> | |||
| <TableContainer component={Paper} sx={{ width: 'fit-content', overflow: 'auto' }}> | |||
| {/* <TableContainer component={Paper} sx={{width: getCustomWidth(), overflow: 'auto' }}> */} | |||
| <Table aria-label="collapsible table" stickyHeader> | |||
| <TableHead> | |||
| <TableRow> | |||
| @@ -147,7 +147,7 @@ function PoInputGrid({ | |||
| 0, | |||
| ); | |||
| setProcessedQty(processedQty); | |||
| }, [entries]); | |||
| }, [entries, setProcessedQty]); | |||
| const handleDelete = useCallback( | |||
| (id: GridRowId) => () => { | |||
| @@ -155,6 +155,42 @@ function PoInputGrid({ | |||
| }, | |||
| [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); | |||
| @@ -189,7 +225,7 @@ function PoInputGrid({ | |||
| // openStartModal(); | |||
| }, 200); | |||
| }, | |||
| [createStockInLine], | |||
| [setStockInLine], | |||
| ); | |||
| const fetchQcDefaultValue = useCallback(async (stockInLineId: GridRowId) => { | |||
| return await fetchQcResult(stockInLineId as number); | |||
| @@ -217,7 +253,7 @@ function PoInputGrid({ | |||
| setBtnIsLoading(false); | |||
| }, 200); | |||
| }, | |||
| [fetchQcDefaultValue], | |||
| [fetchQcDefaultValue, openQcModal], | |||
| ); | |||
| const handleEscalation = useCallback( | |||
| (id: GridRowId, params: any) => () => { | |||
| @@ -234,7 +270,7 @@ function PoInputGrid({ | |||
| // setBtnIsLoading(false); | |||
| }, 200); | |||
| }, | |||
| [], | |||
| [openEscalationModal], | |||
| ); | |||
| const handleReject = useCallback( | |||
| @@ -254,7 +290,7 @@ function PoInputGrid({ | |||
| // printQrcode(params.row); | |||
| }, 200); | |||
| }, | |||
| [], | |||
| [openRejectModal], | |||
| ); | |||
| const handleStockIn = useCallback( | |||
| @@ -274,7 +310,7 @@ function PoInputGrid({ | |||
| // setBtnIsLoading(false); | |||
| }, 200); | |||
| }, | |||
| [], | |||
| [openStockInModal], | |||
| ); | |||
| const handlePutAway = useCallback( | |||
| @@ -294,7 +330,7 @@ function PoInputGrid({ | |||
| // setBtnIsLoading(false); | |||
| }, 200); | |||
| }, | |||
| [], | |||
| [openPutAwayModal], | |||
| ); | |||
| const printQrcode = useCallback( | |||
| @@ -310,79 +346,47 @@ function PoInputGrid({ | |||
| } | |||
| setBtnIsLoading(false); | |||
| }, | |||
| [fetchPoQrcode, downloadFile], | |||
| ); | |||
| 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); | |||
| }, | |||
| [], | |||
| ); | |||
| 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 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<GridColDef[]>( | |||
| () => [ | |||
| { | |||
| field: "itemNo", | |||
| headerName: t("itemNo"), | |||
| flex: 0.4, | |||
| width: 120, | |||
| // flex: 0.4, | |||
| }, | |||
| { | |||
| field: "itemName", | |||
| headerName: t("itemName"), | |||
| flex: 0.6, | |||
| width: 120, | |||
| // flex: 0.6, | |||
| }, | |||
| { | |||
| field: "acceptedQty", | |||
| headerName: t("acceptedQty"), | |||
| flex: 0.5, | |||
| // flex: 0.5, | |||
| width: 120, | |||
| type: "number", | |||
| // editable: true, | |||
| // replace with tooltip + content | |||
| @@ -390,7 +394,8 @@ function PoInputGrid({ | |||
| { | |||
| field: "uom", | |||
| headerName: t("uom"), | |||
| flex: 0.5, | |||
| width: 120, | |||
| // flex: 0.5, | |||
| renderCell: (params) => { | |||
| return params.row.uom.code; | |||
| }, | |||
| @@ -398,7 +403,8 @@ function PoInputGrid({ | |||
| { | |||
| field: "weight", | |||
| headerName: t("weight"), | |||
| flex: 0.5, | |||
| width: 120, | |||
| // flex: 0.5, | |||
| renderCell: (params) => { | |||
| const weight = calculateWeight( | |||
| params.row.acceptedQty, | |||
| @@ -411,7 +417,8 @@ function PoInputGrid({ | |||
| { | |||
| field: "status", | |||
| headerName: t("status"), | |||
| flex: 0.5, | |||
| width: 120, | |||
| // flex: 0.5, | |||
| renderCell: (params) => { | |||
| return t(`${params.row.status}`); | |||
| }, | |||
| @@ -423,7 +430,8 @@ function PoInputGrid({ | |||
| "stock in", | |||
| )} | ${t("putaway")} | ${t("delete")}`, | |||
| // headerName: "start | qc | escalation | stock in | putaway | delete", | |||
| flex: 1.5, | |||
| width: 300, | |||
| // flex: 1.5, | |||
| cellClassName: "actions", | |||
| getActions: (params) => { | |||
| // console.log(params.row.status); | |||
| @@ -494,7 +502,7 @@ function PoInputGrid({ | |||
| (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)} | |||
| @@ -560,7 +568,7 @@ function PoInputGrid({ | |||
| }, | |||
| }, | |||
| ], | |||
| [stockInLineStatusMap, btnIsLoading, handleQrCode, handleReject], | |||
| [t, handleStart, handleQC, handleEscalation, session?.user?.abilities, handleStockIn, handlePutAway, handleDelete, handleReject], | |||
| ); | |||
| const addRow = useCallback(() => { | |||
| @@ -585,7 +593,7 @@ function PoInputGrid({ | |||
| // fieldToFocus: "projectId", | |||
| }, | |||
| })); | |||
| }, [currQty, getRowId]); | |||
| }, [currQty, getRowId, itemDetail]); | |||
| const validation = useCallback( | |||
| ( | |||
| newRow: GridRowModel<StockInLineRow>, | |||
| @@ -599,7 +607,7 @@ function PoInputGrid({ | |||
| } | |||
| return Object.keys(error).length > 0 ? error : undefined; | |||
| }, | |||
| [currQty], | |||
| [currQty, itemDetail.qty, t], | |||
| ); | |||
| const processRowUpdate = useCallback( | |||
| ( | |||
| @@ -632,7 +640,7 @@ function PoInputGrid({ | |||
| setCurrQty(total); | |||
| return rowToSave; | |||
| }, | |||
| [getRowId, entries], | |||
| [validation, entries, setStockInLine, getRowId], | |||
| ); | |||
| const onProcessRowUpdateError = useCallback( | |||
| @@ -27,7 +27,7 @@ | |||
| "total weight": "總重量", | |||
| "weight unit": "重量單位", | |||
| "price": "價格", | |||
| "processed": "已入倉", | |||
| "processed": "已處理", | |||
| "expiryDate": "到期日", | |||
| "acceptedQty": "接受數量", | |||
| "weight": "重量", | |||