| @@ -16,7 +16,6 @@ export interface PostStockInLiineResponse<T> { | |||
| entity: StockInLine | StockInLine[] | |||
| } | |||
| export interface StockInLineEntry { | |||
| id?: number | |||
| itemId: number | |||
| @@ -24,19 +23,37 @@ export interface StockInLineEntry { | |||
| purchaseOrderLineId: number | |||
| acceptedQty: number | |||
| status?: string | |||
| expiryDate?: string | |||
| } | |||
| export interface PurchaseQcCheck { | |||
| qcCheckId: number; | |||
| qty: number; | |||
| } | |||
| export interface StockInInput { | |||
| productLotNo?: string, | |||
| receiptDate: string | |||
| acceptedQty: number | |||
| acceptedWeight?: number | |||
| productionDate?: string | |||
| expiryDate: string | |||
| } | |||
| export interface PurchaseQCInput { | |||
| sampleRate: number; | |||
| sampleWeight: number; | |||
| totalWeight: number; | |||
| qcCheck: PurchaseQcCheck[]; | |||
| } | |||
| export interface EscalationInput { | |||
| handler: string | |||
| stockInLine: StockInLineEntry[] | |||
| } | |||
| export interface PutawayInput { | |||
| handler: string | |||
| stockInLine: StockInLineEntry[] | |||
| } | |||
| export type ModalFormInput = Partial<PurchaseQCInput & StockInInput & EscalationInput & PutawayInput> | |||
| export const testFetch = cache(async (id: number) => { | |||
| return serverFetchJson<PoResult>(`${BASE_API_URL}/po/detail/${id}`, { | |||
| @@ -53,8 +70,8 @@ export const createStockInLine = async (data: StockInLineEntry) => { | |||
| return stockInLine | |||
| } | |||
| export const updateStockInLine = async (data: StockInLineEntry) => { | |||
| const stockInLine = await serverFetchJson<PostStockInLiineResponse<StockInLineEntry>>(`${BASE_API_URL}/stockInLine/update`, { | |||
| export const updateStockInLine = async (data: StockInLineEntry & ModalFormInput) => { | |||
| const stockInLine = await serverFetchJson<PostStockInLiineResponse<StockInLineEntry & ModalFormInput>>(`${BASE_API_URL}/stockInLine/update`, { | |||
| method: "POST", | |||
| body: JSON.stringify(data), | |||
| headers: { "Content-Type": "application/json" }, | |||
| @@ -20,6 +20,7 @@ export interface PurchaseOrderLine { | |||
| itemNo: string | |||
| itemName: string | |||
| qty: number | |||
| uom?: string | |||
| price: number | |||
| status: string | |||
| stockInLine: StockInLine[] | |||
| @@ -45,7 +45,8 @@ export const serverFetch: typeof fetch = async (input, init) => { | |||
| ...(accessToken | |||
| ? { | |||
| Authorization: `Bearer ${accessToken}`, | |||
| Accept: | |||
| "application/json, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, multipart/form-data", | |||
| } | |||
| : {}), | |||
| }, | |||
| @@ -8,13 +8,14 @@ export const moneyFormatter = new Intl.NumberFormat("en-HK", { | |||
| currency: "HKD", | |||
| }); | |||
| export const stockInLineStatusMap: { [status: string]: number } = { | |||
| draft: 0, | |||
| pending: 1, | |||
| qc: 2, | |||
| determine1: 3, | |||
| determine2: 4, | |||
| determine3: 5, | |||
| receiving: 6, | |||
| completed: 7, | |||
| export const stockInLineStatusMap: { [status: string]: {key: string, value: number} } = { | |||
| draft: { key: "draft", value: 0 }, | |||
| pending: { key: "pending", value: 1 }, | |||
| qc: { key: "qc", value: 2 }, | |||
| determine1: { key: "determine1", value: 3 }, | |||
| determine2: { key: "determine2", value: 4 }, | |||
| determine3: { key: "determine3", value: 5 }, | |||
| receiving: { key: "receiving", value: 6 }, | |||
| received: { key: "received", value: 7 }, | |||
| completed: { key: "completed", value: 8 }, | |||
| }; | |||
| @@ -0,0 +1,206 @@ | |||
| "use client"; | |||
| import { StockInLineEntry, EscalationInput } from "@/app/api/po/actions"; | |||
| import { | |||
| Box, | |||
| Card, | |||
| CardContent, | |||
| Grid, | |||
| Stack, | |||
| TextField, | |||
| Tooltip, | |||
| Typography, | |||
| } from "@mui/material"; | |||
| import { useFormContext } from "react-hook-form"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import StyledDataGrid from "../StyledDataGrid"; | |||
| import { useCallback, useMemo } from "react"; | |||
| import { | |||
| GridColDef, | |||
| GridRowIdGetter, | |||
| GridRowModel, | |||
| useGridApiContext, | |||
| GridRenderCellParams, | |||
| GridRenderEditCellParams, | |||
| useGridApiRef, | |||
| } from "@mui/x-data-grid"; | |||
| import InputDataGrid from "../InputDataGrid"; | |||
| import { TableRow } from "../InputDataGrid/InputDataGrid"; | |||
| import TwoLineCell from "./TwoLineCell"; | |||
| import QcSelect from "./QcSelect"; | |||
| import { QcItemWithChecks } from "@/app/api/qc"; | |||
| import { GridEditInputCell } from "@mui/x-data-grid"; | |||
| import { StockInLine } from "@/app/api/po"; | |||
| interface Props { | |||
| itemDetail: StockInLine; | |||
| // qc: QcItemWithChecks[]; | |||
| } | |||
| type EntryError = | |||
| | { | |||
| [field in keyof StockInLineEntry]?: string; | |||
| } | |||
| | undefined; | |||
| type PoEscalationRow = TableRow<Partial<StockInLineEntry>, EntryError>; | |||
| const EscalationForm: React.FC<Props> = ({ | |||
| // qc, | |||
| itemDetail, | |||
| }) => { | |||
| const { t } = useTranslation(); | |||
| const apiRef = useGridApiRef(); | |||
| const { | |||
| register, | |||
| formState: { errors, defaultValues, touchedFields }, | |||
| watch, | |||
| control, | |||
| setValue, | |||
| getValues, | |||
| reset, | |||
| resetField, | |||
| setError, | |||
| clearErrors, | |||
| } = useFormContext<EscalationInput>(); | |||
| console.log(itemDetail) | |||
| const columns = useMemo<GridColDef[]>( | |||
| () => [ | |||
| // { | |||
| // field: "qcCheckId", | |||
| // headerName: "qc Check", | |||
| // flex: 1, | |||
| // editable: true, | |||
| // valueFormatter(params) { | |||
| // const row = params.id ? params.api.getRow<PoEscalationRow>(params.id) : null; | |||
| // if (!row) { | |||
| // return null; | |||
| // } | |||
| // const Qc = qc.find((q) => q.id === row.qcCheckId); | |||
| // return Qc ? `${Qc.code} - ${Qc.name}` : t("Please select QC"); | |||
| // }, | |||
| // renderCell(params: GridRenderCellParams<PoEscalationRow, number>) { | |||
| // console.log(params.value); | |||
| // return <TwoLineCell>{params.formattedValue}</TwoLineCell>; | |||
| // }, | |||
| // renderEditCell(params: GridRenderEditCellParams<PoEscalationRow, number>) { | |||
| // const errorMessage = | |||
| // params.row._error?.[params.field as keyof StockInLineEntry]; | |||
| // console.log(errorMessage); | |||
| // const content = ( | |||
| // <QcSelect | |||
| // allQcs={qc} | |||
| // value={params.row.qcCheckId} | |||
| // onQcSelect={async (qcCheckId) => { | |||
| // await params.api.setEditCellValue({ | |||
| // id: params.id, | |||
| // field: "qcCheckId", | |||
| // value: qcCheckId, | |||
| // }); | |||
| // }} | |||
| // /> | |||
| // ); | |||
| // return errorMessage ? ( | |||
| // <Tooltip title={t(errorMessage)}> | |||
| // <Box width="100%">{content}</Box> | |||
| // </Tooltip> | |||
| // ) : ( | |||
| // content | |||
| // ); | |||
| // }, | |||
| // }, | |||
| { | |||
| field: "qty", | |||
| headerName: "qty", | |||
| flex: 1, | |||
| editable: true, | |||
| type: "number", | |||
| renderEditCell(params: GridRenderEditCellParams<PoEscalationRow>) { | |||
| const errorMessage = | |||
| params.row._error?.[params.field as keyof StockInLineEntry]; | |||
| const content = <GridEditInputCell {...params} />; | |||
| return errorMessage ? ( | |||
| <Tooltip title={t(errorMessage)}> | |||
| <Box width="100%">{content}</Box> | |||
| </Tooltip> | |||
| ) : ( | |||
| content | |||
| ); | |||
| }, | |||
| }, | |||
| ], | |||
| [] | |||
| ); | |||
| const validationTest = useCallback( | |||
| (newRow: GridRowModel<PoEscalationRow>): EntryError => { | |||
| const error: EntryError = {}; | |||
| // const { qcCheckId, qty } = newRow; | |||
| // if (!qcCheckId || qcCheckId <= 0) { | |||
| // error["qcCheckId"] = "select qc"; | |||
| // } | |||
| // if (!qty || qty <= 0) { | |||
| // error["qty"] = "enter a qty"; | |||
| // } | |||
| return Object.keys(error).length > 0 ? error : undefined; | |||
| }, | |||
| [] | |||
| ); | |||
| return ( | |||
| <Grid container justifyContent="flex-start" alignItems="flex-start"> | |||
| <Grid item xs={12}> | |||
| <Typography variant="h6" display="block" marginBlockEnd={1}> | |||
| {t("Qc Detail")} | |||
| </Typography> | |||
| </Grid> | |||
| <Grid | |||
| container | |||
| justifyContent="flex-start" | |||
| alignItems="flex-start" | |||
| spacing={2} | |||
| sx={{ mt: 0.5 }} | |||
| > | |||
| <Grid item xs={4}> | |||
| <TextField | |||
| label={t("handler")} | |||
| fullWidth | |||
| {...register("handler", { | |||
| required: "handler required!", | |||
| })} | |||
| error={Boolean(errors.handler)} | |||
| helperText={errors.handler?.message} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={4}> | |||
| <TextField | |||
| label={t("total")} | |||
| fullWidth | |||
| // {...register("handler", { | |||
| // required: "handler required!", | |||
| // })} | |||
| value={itemDetail.acceptedQty} | |||
| disabled | |||
| // error={Boolean(errors.handler)} | |||
| // helperText={errors.handler?.message} | |||
| /> | |||
| </Grid> | |||
| </Grid> | |||
| <Grid | |||
| container | |||
| justifyContent="flex-start" | |||
| alignItems="flex-start" | |||
| spacing={2} | |||
| sx={{ mt: 0.5 }} | |||
| > | |||
| <Grid item xs={12}> | |||
| <InputDataGrid<EscalationInput, StockInLineEntry, EntryError> | |||
| apiRef={apiRef} | |||
| checkboxSelection={false} | |||
| _formKey={"stockInLine"} | |||
| columns={columns} | |||
| validateRow={validationTest} | |||
| /> | |||
| </Grid> | |||
| </Grid> | |||
| </Grid> | |||
| ); | |||
| }; | |||
| export default EscalationForm; | |||
| @@ -77,7 +77,7 @@ const PoDetail: React.FC<Props> = ({ | |||
| <TableCell align="left">{row.itemNo}</TableCell> | |||
| <TableCell align="left">{row.itemName}</TableCell> | |||
| <TableCell align="left">{row.qty}</TableCell> | |||
| {/* <TableCell align="left">{row.uom}</TableCell> */} | |||
| <TableCell align="left">{row.uom}</TableCell> | |||
| <TableCell align="left">{row.price}</TableCell> | |||
| {/* <TableCell align="left">{row.expiryDate}</TableCell> */} | |||
| <TableCell align="left">{row.status}</TableCell> | |||
| @@ -133,6 +133,7 @@ const PoDetail: React.FC<Props> = ({ | |||
| <TableCell>{t("itemNo")}</TableCell> | |||
| <TableCell align="left">{t("itemName")}</TableCell> | |||
| <TableCell align="left">{t("qty")}</TableCell> | |||
| <TableCell align="left">{t("uom")}</TableCell> | |||
| <TableCell align="left">{t("price")}</TableCell> | |||
| {/* <TableCell align="left">{t("expiryDate")}</TableCell> */} | |||
| <TableCell align="left">{t("status")}</TableCell> | |||
| @@ -30,14 +30,14 @@ 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 PoQcModal from "./PoQcModal"; | |||
| import { QcItemWithChecks } from "src/app/api/qc"; | |||
| import PlayArrowIcon from "@mui/icons-material/PlayArrow"; | |||
| import { PurchaseOrderLine, StockInLine } from "@/app/api/po"; | |||
| import { createStockInLine, testFetch } from "@/app/api/po/actions"; | |||
| import { useSearchParams } from "next/navigation"; | |||
| import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | |||
| import PoQcStockInModal from "./PoQcStockInModal"; | |||
| import NotificationImportantIcon from '@mui/icons-material/NotificationImportant'; | |||
| interface ResultWithId { | |||
| id: number; | |||
| } | |||
| @@ -85,19 +85,23 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||
| const [entries, setEntries] = useState<StockInLineRow[]>(stockInLine || []); | |||
| const [modalInfo, setModalInfo] = useState<StockInLine>() | |||
| const [qcOpen, setQcOpen] = useState(false); | |||
| const [escalOpen, setEscalOpen] = useState(false); | |||
| const [stockInOpen, setStockInOpen] = useState(false); | |||
| const [putAwayOpen, setPutAwayOpen] = useState(false); | |||
| const [type, setType] = useState<"qc" | "stockIn">("qc"); | |||
| const [defaultQty, setDefaultQty] = useState(() => { | |||
| const total = entries.reduce((acc, curr) => acc + (curr.acceptedQty || 0), 0); | |||
| return itemDetail.qty - total; | |||
| }); | |||
| const params = useSearchParams() | |||
| const refetchData = useCallback(async () => { | |||
| const id = parseInt(params.get("id")!!) | |||
| const res = await testFetch(id) | |||
| const pol = res.pol!! | |||
| console.log(pol) | |||
| setRows(pol); | |||
| }, [params]) | |||
| // const refetchData = useCallback(async () => { | |||
| // const id = parseInt(params.get("id")!!) | |||
| // const res = await testFetch(id) | |||
| // const pol = res.pol!! | |||
| // console.log(pol) | |||
| // setRows(pol); | |||
| // }, [params]) | |||
| const handleDelete = useCallback( | |||
| (id: GridRowId) => () => { | |||
| @@ -127,7 +131,7 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||
| } | |||
| const res = await createStockInLine(postData) | |||
| console.log(res) | |||
| // setEntries((prev) => prev.map((p) => p.id === oldId ? res.entity : p)) | |||
| setEntries((prev) => prev.map((p) => p.id === oldId ? res.entity as StockInLine : p)) | |||
| // do post directly to test | |||
| // openStartModal(); | |||
| }, 200); | |||
| @@ -149,14 +153,49 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||
| }, | |||
| [] | |||
| ); | |||
| const handleEscalation = useCallback( | |||
| (id: GridRowId, params: any) => () => { | |||
| setRowModesModel((prev) => ({ | |||
| ...prev, | |||
| [id]: { mode: GridRowModes.View }, | |||
| })); | |||
| setModalInfo(params.row) | |||
| setTimeout(() => { | |||
| // open qc modal | |||
| console.log("delayed"); | |||
| openEscalationModal() | |||
| }, 200); | |||
| }, | |||
| [] | |||
| ); | |||
| const handleStockIn = useCallback( | |||
| (id: GridRowId) => () => { | |||
| (id: GridRowId, params: any) => () => { | |||
| 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"); | |||
| }, 200); | |||
| }, | |||
| [] | |||
| ); | |||
| const handlePutAway = 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"); | |||
| @@ -172,11 +211,32 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||
| 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 columns = useMemo<GridColDef[]>( | |||
| () => [ | |||
| { | |||
| field: "itemNo", | |||
| flex: 1, | |||
| flex: .8, | |||
| }, | |||
| { | |||
| field: "itemName", | |||
| @@ -198,20 +258,10 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||
| { | |||
| field: "actions", | |||
| type: "actions", | |||
| headerName: "start | qc | stock in | delete", | |||
| flex: 1, | |||
| headerName: "start | qc | escalate | stock in | putaway | delete", | |||
| flex: 1.5, | |||
| cellClassName: "actions", | |||
| getActions: (params) => { | |||
| // const stockInLineStatusMap: { [status: string]: number } = { | |||
| // draft: 0, | |||
| // pending: 1, | |||
| // qc: 2, | |||
| // determine1: 3, | |||
| // determine2: 4, | |||
| // determine3: 5, | |||
| // receiving: 6, | |||
| // completed: 7, | |||
| // }; | |||
| console.log(params.row.status); | |||
| const status = params.row.status.toLowerCase() | |||
| return [ | |||
| @@ -220,8 +270,9 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||
| label="start" | |||
| sx={{ | |||
| color: "primary.main", | |||
| marginRight: 2 | |||
| }} | |||
| disabled={!(stockInLineStatusMap[status] === 0)} | |||
| disabled={!(stockInLineStatusMap[status].value === 0)} | |||
| // set _isNew to false after posting | |||
| // or check status | |||
| onClick={handleStart(params.row.id, params)} | |||
| @@ -233,24 +284,54 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||
| label="qc" | |||
| sx={{ | |||
| color: "primary.main", | |||
| marginRight: 2 | |||
| }} | |||
| disabled={stockInLineStatusMap[status] <= 0 || stockInLineStatusMap[status] >= 6} | |||
| disabled={stockInLineStatusMap[status].value <= 0 || stockInLineStatusMap[status].value >= 5} | |||
| // set _isNew to false after posting | |||
| // or check status | |||
| onClick={handleQC(params.row.id, params)} | |||
| color="inherit" | |||
| key="edit" | |||
| />, | |||
| <GridActionsCellItem | |||
| icon={<NotificationImportantIcon />} | |||
| label="escalation" | |||
| sx={{ | |||
| color: "primary.main", | |||
| marginRight: 2 | |||
| }} | |||
| disabled={stockInLineStatusMap[status].value <= 0 || stockInLineStatusMap[status].value >= 5} | |||
| // set _isNew to false after posting | |||
| // or check status | |||
| onClick={handleEscalation(params.row.id, params)} | |||
| color="inherit" | |||
| key="edit" | |||
| />, | |||
| <GridActionsCellItem | |||
| icon={<ShoppingCartIcon />} | |||
| label="stockin" | |||
| sx={{ | |||
| color: "primary.main", | |||
| marginRight: 2 | |||
| }} | |||
| disabled={stockInLineStatusMap[status].value !== 6} | |||
| // set _isNew to false after posting | |||
| // or check status | |||
| onClick={handleStockIn(params.row.id, params)} | |||
| color="inherit" | |||
| key="edit" | |||
| />, | |||
| <GridActionsCellItem | |||
| icon={<ShoppingCartIcon />} | |||
| label="putaway" | |||
| sx={{ | |||
| color: "primary.main", | |||
| marginRight: 2 | |||
| }} | |||
| disabled={stockInLineStatusMap[status] !== 6} | |||
| disabled={stockInLineStatusMap[status].value !== 7} | |||
| // set _isNew to false after posting | |||
| // or check status | |||
| onClick={handleStockIn(params.row.id)} | |||
| onClick={handlePutAway(params.row.id, params)} | |||
| color="inherit" | |||
| key="edit" | |||
| />, | |||
| @@ -260,7 +341,7 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||
| sx={{ | |||
| color: "error.main", | |||
| }} | |||
| disabled={stockInLineStatusMap[status] !== 0} | |||
| disabled={stockInLineStatusMap[status].value !== 0} | |||
| // disabled={Boolean(params.row.status)} | |||
| onClick={handleDelete(params.row.id)} | |||
| color="inherit" | |||
| @@ -404,7 +485,8 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||
| }} | |||
| /> | |||
| <> | |||
| <PoQcModal | |||
| <PoQcStockInModal | |||
| type={"qc"} | |||
| setEntries={setEntries} | |||
| qc={qc} | |||
| open={qcOpen} | |||
| @@ -412,6 +494,36 @@ function PoInputGrid({ qc, setRows, itemDetail, stockInLine }: Props) { | |||
| itemDetail={modalInfo!!} | |||
| /> | |||
| </> | |||
| <> | |||
| <PoQcStockInModal | |||
| type={"escalation"} | |||
| setEntries={setEntries} | |||
| // qc={qc} | |||
| open={escalOpen} | |||
| onClose={closeEscalationModal} | |||
| itemDetail={modalInfo!!} | |||
| /> | |||
| </> | |||
| <> | |||
| <PoQcStockInModal | |||
| type={"stockIn"} | |||
| setEntries={setEntries} | |||
| // qc={qc} | |||
| open={stockInOpen} | |||
| onClose={closeStockInModal} | |||
| itemDetail={modalInfo!!} | |||
| /> | |||
| </> | |||
| <> | |||
| <PoQcStockInModal | |||
| type={"putaway"} | |||
| setEntries={setEntries} | |||
| open={putAwayOpen} | |||
| warehouse={[]} | |||
| onClose={closePutAwayModal} | |||
| itemDetail={modalInfo!!} | |||
| /> | |||
| </> | |||
| </> | |||
| ); | |||
| } | |||
| @@ -1,8 +1,8 @@ | |||
| "use client"; | |||
| import { PurchaseQCInput, StockInLineEntry, updateStockInLine } from "@/app/api/po/actions"; | |||
| import { ModalFormInput, PurchaseQCInput, StockInInput, StockInLineEntry, updateStockInLine } from "@/app/api/po/actions"; | |||
| import { Box, Button, Modal, ModalProps, Stack } from "@mui/material"; | |||
| import { Dispatch, SetStateAction, useCallback, useEffect, useState } from "react"; | |||
| import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react"; | |||
| import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import QcForm from "./QcForm"; | |||
| @@ -12,24 +12,36 @@ import { StockInLine } from "@/app/api/po"; | |||
| import { useSearchParams } from "next/navigation"; | |||
| import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | |||
| import { StockInLineRow } from "./PoInputGrid"; | |||
| // type: | |||
| import EscalationForm from "./EscalationForm"; | |||
| import StockInForm from "./StockInForm"; | |||
| interface CommonProps extends Omit<ModalProps, "children"> { | |||
| setEntries: Dispatch<SetStateAction<StockInLineRow[]>> | |||
| itemDetail: StockInLine; | |||
| qc?: QcItemWithChecks[]; | |||
| warehouse?: any[]; | |||
| type: "qc" | "stockIn" | "escalation" | "putaway" | |||
| } | |||
| interface QcProps extends CommonProps { | |||
| qc: QcItemWithChecks[]; | |||
| type: "qc" | |||
| } | |||
| interface StockInProps extends CommonProps { | |||
| // naming | |||
| type: "stockIn" | |||
| } | |||
| interface PutawayProps extends CommonProps { | |||
| // naming | |||
| // warehouse: any[]; | |||
| warehouse: any[]; | |||
| type: "putaway" | |||
| } | |||
| interface EscalationProps extends CommonProps { | |||
| // naming | |||
| type: "escalation" | |||
| } | |||
| type Props = QcProps | StockInProps; | |||
| type Props = QcProps | StockInProps | EscalationProps | PutawayProps; | |||
| const style = { | |||
| position: "absolute", | |||
| @@ -42,7 +54,8 @@ const style = { | |||
| pb: 10, | |||
| width: { xs: "80%", sm: "80%", md: "80%" }, | |||
| }; | |||
| const PoQcModal: React.FC<Props> = ({ | |||
| const PoQcStockInModal: React.FC<Props> = ({ | |||
| type, | |||
| setEntries, | |||
| open, | |||
| onClose, | |||
| @@ -56,7 +69,7 @@ const PoQcModal: React.FC<Props> = ({ | |||
| const params = useSearchParams() | |||
| console.log(params.get("id")) | |||
| const [defaultValues, setDefaultValues] = useState({}); | |||
| const formProps = useForm<PurchaseQCInput>({ | |||
| const formProps = useForm<ModalFormInput>({ | |||
| defaultValues: defaultValues ? defaultValues : {}, | |||
| }); | |||
| const errors = formProps.formState.errors; | |||
| @@ -72,32 +85,84 @@ const PoQcModal: React.FC<Props> = ({ | |||
| setDefaultValues({}); | |||
| }, []); | |||
| const onSubmit = useCallback<SubmitHandler<PurchaseQCInput & {}>>( | |||
| // status to be posted | |||
| const getPostingStatus = useCallback( | |||
| (type: string) => { | |||
| switch (type) { | |||
| case "qc": | |||
| return stockInLineStatusMap.receiving.key; | |||
| case "stockIn": | |||
| return stockInLineStatusMap.received.key; | |||
| case "putaway": | |||
| return stockInLineStatusMap.completed.key; | |||
| default: | |||
| return stockInLineStatusMap.pending.key; | |||
| } | |||
| }, [] | |||
| ) | |||
| const onSubmit = useCallback<SubmitHandler<ModalFormInput & {}>>( | |||
| async (data, event) => { | |||
| let hasErrors = false; | |||
| console.log(errors); | |||
| console.log(data); | |||
| console.log(itemDetail); | |||
| try { | |||
| if (hasErrors) { | |||
| setServerError(t("An error has occurred. Please try again later.")); | |||
| return false; | |||
| console.log(type) | |||
| var status = getPostingStatus(type) | |||
| // if escalation, take data.status as status | |||
| console.log(status) | |||
| // add checking | |||
| // const qty = data.sampleRate | |||
| //////////////////////// modify this mess later ////////////////////// | |||
| var productionDate = null | |||
| var acceptedQty = null | |||
| if (data.productionDate && data.productionDate.length > 0) { | |||
| productionDate = data.productionDate | |||
| } | |||
| if (data.acceptedQty) { | |||
| acceptedQty = parseInt(data.acceptedQty.toString()) | |||
| } else { | |||
| acceptedQty = data.sampleRate | |||
| } | |||
| // do post update stock in line | |||
| // const reqStatus = stockInLineStatusMap | |||
| const args: StockInLineEntry = { | |||
| const args = { | |||
| id: itemDetail.id, | |||
| purchaseOrderId: parseInt(params.get("id")!!), | |||
| purchaseOrderLineId: itemDetail.purchaseOrderLineId, | |||
| itemId: itemDetail.itemId, | |||
| acceptedQty: itemDetail.acceptedQty, | |||
| status: "receiving", | |||
| } | |||
| ...data, | |||
| acceptedQty: acceptedQty, | |||
| productionDate: productionDate, | |||
| status: status, | |||
| } as StockInLineEntry & ModalFormInput; | |||
| ////////////////////////////////////////////////////////////////////// | |||
| console.log(args) | |||
| // return | |||
| if (hasErrors) { | |||
| setServerError(t("An error has occurred. Please try again later.")); | |||
| return false; | |||
| } | |||
| const res = await updateStockInLine(args) | |||
| // this.res.entity = list of entity | |||
| for (const inLine in res.entity as StockInLine[]) { | |||
| if (Boolean(res.id)) { | |||
| // set entries | |||
| const newEntries = res.entity as StockInLine[] | |||
| setEntries((prev) => { | |||
| const updatedEntries = [...prev]; // Create a new array | |||
| newEntries.forEach((item) => { | |||
| const index = updatedEntries.findIndex(p => p.id === item.id); | |||
| if (index !== -1) { | |||
| // Update existing item | |||
| updatedEntries[index] = item; | |||
| } else { | |||
| // Add new item | |||
| updatedEntries.push(item); | |||
| } | |||
| }); | |||
| return updatedEntries; // Return the new array | |||
| }) | |||
| // add loading | |||
| closeHandler({}, "backdropClick") | |||
| } | |||
| console.log(res) | |||
| @@ -110,7 +175,7 @@ const PoQcModal: React.FC<Props> = ({ | |||
| }, | |||
| [t, itemDetail] | |||
| ); | |||
| return ( | |||
| <> | |||
| <Modal open={open} onClose={closeHandler}> | |||
| @@ -120,7 +185,9 @@ const PoQcModal: React.FC<Props> = ({ | |||
| component="form" | |||
| onSubmit={formProps.handleSubmit(onSubmit)} | |||
| > | |||
| {qc && <QcForm qc={qc} itemDetail={itemDetail} />} | |||
| {type === "qc" && <QcForm qc={qc} itemDetail={itemDetail} />} | |||
| {type === "stockIn" && <StockInForm itemDetail={itemDetail} />} | |||
| {type === "escalation" && <EscalationForm itemDetail={itemDetail} />} | |||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | |||
| <Button | |||
| name="submit" | |||
| @@ -138,4 +205,4 @@ const PoQcModal: React.FC<Props> = ({ | |||
| </> | |||
| ); | |||
| }; | |||
| export default PoQcModal; | |||
| export default PoQcStockInModal; | |||
| @@ -0,0 +1,249 @@ | |||
| "use client"; | |||
| import { PurchaseQcCheck, PurchaseQCInput } from "@/app/api/po/actions"; | |||
| import { | |||
| Box, | |||
| Card, | |||
| CardContent, | |||
| Grid, | |||
| Stack, | |||
| TextField, | |||
| Tooltip, | |||
| Typography, | |||
| } from "@mui/material"; | |||
| import { useFormContext } from "react-hook-form"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import StyledDataGrid from "../StyledDataGrid"; | |||
| import { useCallback, useMemo, useState } from "react"; | |||
| import { | |||
| GridColDef, | |||
| GridRowIdGetter, | |||
| GridRowModel, | |||
| useGridApiContext, | |||
| GridRenderCellParams, | |||
| GridRenderEditCellParams, | |||
| useGridApiRef, | |||
| } from "@mui/x-data-grid"; | |||
| import InputDataGrid from "../InputDataGrid"; | |||
| import { TableRow } from "../InputDataGrid/InputDataGrid"; | |||
| import TwoLineCell from "./TwoLineCell"; | |||
| import QcSelect from "./QcSelect"; | |||
| import { QcItemWithChecks } from "@/app/api/qc"; | |||
| import { GridEditInputCell } from "@mui/x-data-grid"; | |||
| import { StockInLine } from "@/app/api/po"; | |||
| interface Props { | |||
| itemDetail: StockInLine; | |||
| qc: QcItemWithChecks[]; | |||
| } | |||
| type EntryError = | |||
| | { | |||
| [field in keyof PurchaseQcCheck]?: string; | |||
| } | |||
| | undefined; | |||
| type PoQcRow = TableRow<Partial<PurchaseQcCheck>, EntryError>; | |||
| const PutawayForm: React.FC<Props> = ({ | |||
| qc, | |||
| itemDetail, | |||
| }) => { | |||
| const { t } = useTranslation(); | |||
| const apiRef = useGridApiRef(); | |||
| const { | |||
| register, | |||
| formState: { errors, defaultValues, touchedFields }, | |||
| watch, | |||
| control, | |||
| setValue, | |||
| getValues, | |||
| reset, | |||
| resetField, | |||
| setError, | |||
| clearErrors, | |||
| } = useFormContext<PurchaseQCInput>(); | |||
| console.log(itemDetail) | |||
| const [recordQty, setRecordQty] = useState(0) | |||
| const columns = useMemo<GridColDef[]>( | |||
| () => [ | |||
| { | |||
| field: "qcCheckId", | |||
| headerName: "qc Check", | |||
| flex: 1, | |||
| editable: true, | |||
| valueFormatter(params) { | |||
| const row = params.id ? params.api.getRow<PoQcRow>(params.id) : null; | |||
| if (!row) { | |||
| return null; | |||
| } | |||
| const Qc = qc.find((q) => q.id === row.qcCheckId); | |||
| return Qc ? `${Qc.code} - ${Qc.name}` : t("Please select QC"); | |||
| }, | |||
| renderCell(params: GridRenderCellParams<PoQcRow, number>) { | |||
| console.log(params.value); | |||
| return <TwoLineCell>{params.formattedValue}</TwoLineCell>; | |||
| }, | |||
| renderEditCell(params: GridRenderEditCellParams<PoQcRow, number>) { | |||
| const errorMessage = | |||
| params.row._error?.[params.field as keyof PurchaseQcCheck]; | |||
| console.log(errorMessage); | |||
| const content = ( | |||
| <QcSelect | |||
| allQcs={qc} | |||
| value={params.row.qcCheckId} | |||
| onQcSelect={async (qcCheckId) => { | |||
| await params.api.setEditCellValue({ | |||
| id: params.id, | |||
| field: "qcCheckId", | |||
| value: qcCheckId, | |||
| }); | |||
| }} | |||
| /> | |||
| ); | |||
| return errorMessage ? ( | |||
| <Tooltip title={t(errorMessage)}> | |||
| <Box width="100%">{content}</Box> | |||
| </Tooltip> | |||
| ) : ( | |||
| content | |||
| ); | |||
| }, | |||
| }, | |||
| { | |||
| field: "qty", | |||
| headerName: "qty", | |||
| flex: 1, | |||
| editable: true, | |||
| type: "number", | |||
| renderEditCell(params: GridRenderEditCellParams<PoQcRow>) { | |||
| // const recordQty = params.row.qty | |||
| // if (recordQty !== undefined) { | |||
| // setUnrecordQty((prev) => prev - recordQty) | |||
| // } | |||
| const errorMessage = | |||
| params.row._error?.[params.field as keyof PurchaseQcCheck]; | |||
| const content = <GridEditInputCell {...params} />; | |||
| return errorMessage ? ( | |||
| <Tooltip title={t(errorMessage)}> | |||
| <Box width="100%">{content}</Box> | |||
| </Tooltip> | |||
| ) : ( | |||
| content | |||
| ); | |||
| }, | |||
| }, | |||
| ], | |||
| [] | |||
| ); | |||
| const validation = useCallback( | |||
| (newRow: GridRowModel<PoQcRow>): EntryError => { | |||
| const error: EntryError = {}; | |||
| const { qcCheckId, qty } = newRow; | |||
| if (!qcCheckId || qcCheckId <= 0) { | |||
| error["qcCheckId"] = "select qc"; | |||
| } | |||
| if (!qty || qty <= 0) { | |||
| error["qty"] = "enter a qty"; | |||
| } | |||
| if (qty && qty > itemDetail.acceptedQty) { | |||
| error["qty"] = "qty too big"; | |||
| } | |||
| return Object.keys(error).length > 0 ? error : undefined; | |||
| }, | |||
| [] | |||
| ); | |||
| return ( | |||
| <Grid container justifyContent="flex-start" alignItems="flex-start"> | |||
| <Grid item xs={12}> | |||
| <Typography variant="h6" display="block" marginBlockEnd={1}> | |||
| {t("Qc Detail")} | |||
| </Typography> | |||
| </Grid> | |||
| <Grid | |||
| container | |||
| justifyContent="flex-start" | |||
| alignItems="flex-start" | |||
| spacing={2} | |||
| sx={{ mt: 0.5 }} | |||
| > | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| label={t("Total qty")} | |||
| fullWidth | |||
| value={itemDetail.acceptedQty} | |||
| disabled | |||
| // {...register("sampleRate", { | |||
| // required: "sampleRate required!", | |||
| // })} | |||
| // error={Boolean(errors.sampleRate)} | |||
| // helperText={errors.sampleRate?.message} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| label={t("Total record qty")} | |||
| fullWidth | |||
| value={recordQty} | |||
| disabled | |||
| // {...register("sampleRate", { | |||
| // required: "sampleRate required!", | |||
| // })} | |||
| // error={Boolean(errors.sampleRate)} | |||
| // helperText={errors.sampleRate?.message} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={4}> | |||
| <TextField | |||
| label={t("sampleRate")} | |||
| fullWidth | |||
| {...register("sampleRate", { | |||
| required: "sampleRate required!", | |||
| })} | |||
| error={Boolean(errors.sampleRate)} | |||
| helperText={errors.sampleRate?.message} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={4}> | |||
| <TextField | |||
| label={t("sampleWeight")} | |||
| fullWidth | |||
| {...register("sampleWeight", { | |||
| required: "sampleWeight required!", | |||
| })} | |||
| error={Boolean(errors.sampleWeight)} | |||
| helperText={errors.sampleWeight?.message} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={4}> | |||
| <TextField | |||
| label={t("totalWeight")} | |||
| fullWidth | |||
| {...register("totalWeight", { | |||
| required: "totalWeight required!", | |||
| })} | |||
| error={Boolean(errors.totalWeight)} | |||
| helperText={errors.totalWeight?.message} | |||
| /> | |||
| </Grid> | |||
| </Grid> | |||
| <Grid | |||
| container | |||
| justifyContent="flex-start" | |||
| alignItems="flex-start" | |||
| spacing={2} | |||
| sx={{ mt: 0.5 }} | |||
| > | |||
| <Grid item xs={12}> | |||
| <InputDataGrid<PurchaseQCInput, PurchaseQcCheck, EntryError> | |||
| apiRef={apiRef} | |||
| checkboxSelection={false} | |||
| _formKey={"qcCheck"} | |||
| columns={columns} | |||
| validateRow={validation} | |||
| /> | |||
| </Grid> | |||
| </Grid> | |||
| </Grid> | |||
| ); | |||
| }; | |||
| export default PutawayForm; | |||
| @@ -14,7 +14,7 @@ import { | |||
| import { useFormContext } from "react-hook-form"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import StyledDataGrid from "../StyledDataGrid"; | |||
| import { useCallback, useMemo } from "react"; | |||
| import { useCallback, useMemo, useState } from "react"; | |||
| import { | |||
| GridColDef, | |||
| GridRowIdGetter, | |||
| @@ -63,6 +63,7 @@ const QcForm: React.FC<Props> = ({ | |||
| clearErrors, | |||
| } = useFormContext<PurchaseQCInput>(); | |||
| console.log(itemDetail) | |||
| const [recordQty, setRecordQty] = useState(0) | |||
| const columns = useMemo<GridColDef[]>( | |||
| () => [ | |||
| { | |||
| @@ -115,6 +116,10 @@ const QcForm: React.FC<Props> = ({ | |||
| editable: true, | |||
| type: "number", | |||
| renderEditCell(params: GridRenderEditCellParams<PoQcRow>) { | |||
| // const recordQty = params.row.qty | |||
| // if (recordQty !== undefined) { | |||
| // setUnrecordQty((prev) => prev - recordQty) | |||
| // } | |||
| const errorMessage = | |||
| params.row._error?.[params.field as keyof PurchaseQcCheck]; | |||
| const content = <GridEditInputCell {...params} />; | |||
| @@ -130,7 +135,7 @@ const QcForm: React.FC<Props> = ({ | |||
| ], | |||
| [] | |||
| ); | |||
| const validationTest = useCallback( | |||
| const validation = useCallback( | |||
| (newRow: GridRowModel<PoQcRow>): EntryError => { | |||
| const error: EntryError = {}; | |||
| const { qcCheckId, qty } = newRow; | |||
| @@ -140,6 +145,9 @@ const QcForm: React.FC<Props> = ({ | |||
| if (!qty || qty <= 0) { | |||
| error["qty"] = "enter a qty"; | |||
| } | |||
| if (qty && qty > itemDetail.acceptedQty) { | |||
| error["qty"] = "qty too big"; | |||
| } | |||
| return Object.keys(error).length > 0 ? error : undefined; | |||
| }, | |||
| [] | |||
| @@ -158,6 +166,32 @@ const QcForm: React.FC<Props> = ({ | |||
| spacing={2} | |||
| sx={{ mt: 0.5 }} | |||
| > | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| label={t("Total qty")} | |||
| fullWidth | |||
| value={itemDetail.acceptedQty} | |||
| disabled | |||
| // {...register("sampleRate", { | |||
| // required: "sampleRate required!", | |||
| // })} | |||
| // error={Boolean(errors.sampleRate)} | |||
| // helperText={errors.sampleRate?.message} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={6}> | |||
| <TextField | |||
| label={t("Total record qty")} | |||
| fullWidth | |||
| value={recordQty} | |||
| disabled | |||
| // {...register("sampleRate", { | |||
| // required: "sampleRate required!", | |||
| // })} | |||
| // error={Boolean(errors.sampleRate)} | |||
| // helperText={errors.sampleRate?.message} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={4}> | |||
| <TextField | |||
| label={t("sampleRate")} | |||
| @@ -205,7 +239,7 @@ const QcForm: React.FC<Props> = ({ | |||
| checkboxSelection={false} | |||
| _formKey={"qcCheck"} | |||
| columns={columns} | |||
| validateRow={validationTest} | |||
| validateRow={validation} | |||
| /> | |||
| </Grid> | |||
| </Grid> | |||
| @@ -0,0 +1,168 @@ | |||
| "use client"; | |||
| import { PurchaseQcCheck, PurchaseQCInput, StockInInput } from "@/app/api/po/actions"; | |||
| import { | |||
| Box, | |||
| Card, | |||
| CardContent, | |||
| Grid, | |||
| Stack, | |||
| TextField, | |||
| Tooltip, | |||
| Typography, | |||
| } from "@mui/material"; | |||
| import { useFormContext } from "react-hook-form"; | |||
| import { useTranslation } from "react-i18next"; | |||
| import StyledDataGrid from "../StyledDataGrid"; | |||
| import { useCallback, useMemo } from "react"; | |||
| import { | |||
| GridColDef, | |||
| GridRowIdGetter, | |||
| GridRowModel, | |||
| useGridApiContext, | |||
| GridRenderCellParams, | |||
| GridRenderEditCellParams, | |||
| useGridApiRef, | |||
| } from "@mui/x-data-grid"; | |||
| import InputDataGrid from "../InputDataGrid"; | |||
| import { TableRow } from "../InputDataGrid/InputDataGrid"; | |||
| import TwoLineCell from "./TwoLineCell"; | |||
| import QcSelect from "./QcSelect"; | |||
| import { QcItemWithChecks } from "@/app/api/qc"; | |||
| import { GridEditInputCell } from "@mui/x-data-grid"; | |||
| import { StockInLine } from "@/app/api/po"; | |||
| // change PurchaseQcCheck to stock in entry props | |||
| interface Props { | |||
| itemDetail: StockInLine; | |||
| // qc: QcItemWithChecks[]; | |||
| } | |||
| type EntryError = | |||
| | { | |||
| [field in keyof StockInInput]?: string; | |||
| } | |||
| | undefined; | |||
| // type PoQcRow = TableRow<Partial<PurchaseQcCheck>, EntryError>; | |||
| const StockInForm: React.FC<Props> = ({ | |||
| // qc, | |||
| itemDetail, | |||
| }) => { | |||
| const { t } = useTranslation(); | |||
| const apiRef = useGridApiRef(); | |||
| const { | |||
| register, | |||
| formState: { errors, defaultValues, touchedFields }, | |||
| watch, | |||
| control, | |||
| setValue, | |||
| getValues, | |||
| reset, | |||
| resetField, | |||
| setError, | |||
| clearErrors, | |||
| } = useFormContext<StockInInput>(); | |||
| console.log(itemDetail) | |||
| return ( | |||
| <Grid container justifyContent="flex-start" alignItems="flex-start"> | |||
| <Grid item xs={12}> | |||
| <Typography variant="h6" display="block" marginBlockEnd={1}> | |||
| {t("Qc Detail")} | |||
| </Typography> | |||
| </Grid> | |||
| <Grid | |||
| container | |||
| justifyContent="flex-start" | |||
| alignItems="flex-start" | |||
| spacing={2} | |||
| sx={{ mt: 0.5 }} | |||
| > | |||
| <Grid item xs={4}> | |||
| <TextField | |||
| label={t("productLotNo")} | |||
| fullWidth | |||
| {...register("productLotNo", { | |||
| // required: "productLotNo required!", | |||
| })} | |||
| // error={Boolean(errors.productLotNo)} | |||
| // helperText={errors.productLotNo?.message} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={4}> | |||
| <TextField | |||
| label={t("receiptDate")} | |||
| fullWidth | |||
| {...register("receiptDate", { | |||
| required: "receiptDate required!", | |||
| })} | |||
| error={Boolean(errors.receiptDate)} | |||
| helperText={errors.receiptDate?.message} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={4}> | |||
| <TextField | |||
| label={t("acceptedQty")} | |||
| fullWidth | |||
| {...register("acceptedQty", { | |||
| required: "acceptedQty required!", | |||
| })} | |||
| error={Boolean(errors.acceptedQty)} | |||
| helperText={errors.acceptedQty?.message} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={4}> | |||
| <TextField | |||
| label={t("acceptedWeight")} | |||
| fullWidth | |||
| // {...register("acceptedWeight", { | |||
| // required: "acceptedWeight required!", | |||
| // })} | |||
| error={Boolean(errors.acceptedWeight)} | |||
| helperText={errors.acceptedWeight?.message} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={4}> | |||
| <TextField | |||
| label={t("productionDate")} | |||
| fullWidth | |||
| {...register("productionDate", { | |||
| // required: "productionDate required!", | |||
| })} | |||
| // error={Boolean(errors.productionDate)} | |||
| // helperText={errors.productionDate?.message} | |||
| /> | |||
| </Grid> | |||
| <Grid item xs={4}> | |||
| <TextField | |||
| label={t("expiryDate")} | |||
| fullWidth | |||
| {...register("expiryDate", { | |||
| required: "expiryDate required!", | |||
| })} | |||
| error={Boolean(errors.expiryDate)} | |||
| helperText={errors.expiryDate?.message} | |||
| /> | |||
| </Grid> | |||
| </Grid> | |||
| <Grid | |||
| container | |||
| justifyContent="flex-start" | |||
| alignItems="flex-start" | |||
| spacing={2} | |||
| sx={{ mt: 0.5 }} | |||
| > | |||
| {/* <Grid item xs={12}> | |||
| <InputDataGrid<PurchaseQCInput, PurchaseQcCheck, EntryError> | |||
| apiRef={apiRef} | |||
| checkboxSelection={false} | |||
| _formKey={"qcCheck"} | |||
| columns={columns} | |||
| validateRow={validationTest} | |||
| /> | |||
| </Grid> */} | |||
| </Grid> | |||
| </Grid> | |||
| ); | |||
| }; | |||
| export default StockInForm; | |||