| @@ -14,8 +14,8 @@ export const metadata: Metadata = { | |||||
| title: "Purchase Order", | title: "Purchase Order", | ||||
| }; | }; | ||||
| const production: React.FC = async () => { | |||||
| const { t } = await getServerI18n("claims"); | |||||
| const PurchaseOrder: React.FC = async () => { | |||||
| const { t } = await getServerI18n("purchaseOrder"); | |||||
| // preloadClaims(); | // preloadClaims(); | ||||
| return ( | return ( | ||||
| @@ -45,4 +45,4 @@ const production: React.FC = async () => { | |||||
| ); | ); | ||||
| }; | }; | ||||
| export default production; | |||||
| export default PurchaseOrder; | |||||
| @@ -15,7 +15,8 @@ export interface PostStockInLiineResponse<T> { | |||||
| type?: string | type?: string | ||||
| message: string | null; | message: string | null; | ||||
| errorPosition: string | keyof T; | errorPosition: string | keyof T; | ||||
| entity: StockInLine | StockInLine[] | |||||
| entity: T | T[] | |||||
| // entity: StockInLine | StockInLine[] | |||||
| } | } | ||||
| export interface StockInLineEntry { | export interface StockInLineEntry { | ||||
| @@ -83,6 +84,7 @@ export const createStockInLine = async (data: StockInLineEntry) => { | |||||
| body: JSON.stringify(data), | body: JSON.stringify(data), | ||||
| headers: { "Content-Type": "application/json" }, | headers: { "Content-Type": "application/json" }, | ||||
| }); | }); | ||||
| // revalidateTag("po"); | |||||
| return stockInLine | return stockInLine | ||||
| } | } | ||||
| @@ -92,16 +94,34 @@ export const updateStockInLine = async (data: StockInLineEntry & ModalFormInput) | |||||
| body: JSON.stringify(data), | body: JSON.stringify(data), | ||||
| headers: { "Content-Type": "application/json" }, | headers: { "Content-Type": "application/json" }, | ||||
| }); | }); | ||||
| // revalidateTag("po"); | |||||
| return stockInLine | return stockInLine | ||||
| } | } | ||||
| export const startPo = async (poId: number) => { | |||||
| const po = await serverFetchJson<PostStockInLiineResponse<PoResult>>(`${BASE_API_URL}/po/start/${poId}`, { | |||||
| method: "POST", | |||||
| body: JSON.stringify({ poId }), | |||||
| headers: { "Content-Type": "application/json" }, | |||||
| }); | |||||
| revalidateTag("po"); | |||||
| return po | |||||
| } | |||||
| export const checkPolAndCompletePo = async (poId: number) => { | export const checkPolAndCompletePo = async (poId: number) => { | ||||
| const po = await serverFetchJson<PostStockInLiineResponse<PoResult>>(`${BASE_API_URL}/po//check/${poId}`, { | |||||
| const po = await serverFetchJson<PostStockInLiineResponse<PoResult>>(`${BASE_API_URL}/po/check/${poId}`, { | |||||
| method: "POST", | method: "POST", | ||||
| body: JSON.stringify({ poId }), | body: JSON.stringify({ poId }), | ||||
| headers: { "Content-Type": "application/json" }, | headers: { "Content-Type": "application/json" }, | ||||
| }); | }); | ||||
| revalidateTag("po"); | |||||
| return po | return po | ||||
| } | } | ||||
| export const fetchPoInClient = cache(async (id: number) => { | |||||
| return serverFetchJson<PoResult>(`${BASE_API_URL}/po/detail/${id}`, { | |||||
| next: { tags: ["po"] }, | |||||
| }); | |||||
| }); | |||||
| @@ -41,8 +41,10 @@ export interface StockInLine { | |||||
| acceptedQty: number | acceptedQty: number | ||||
| price: number | price: number | ||||
| priceUnit: string | priceUnit: string | ||||
| productionDate: string | |||||
| expiryDate: string | |||||
| shelfLife?: number, | |||||
| receiptDate?: string | |||||
| productionDate?: string | |||||
| expiryDate?: string | |||||
| status: string | status: string | ||||
| supplier: string | supplier: string | ||||
| lotNo: string | lotNo: string | ||||
| @@ -18,6 +18,12 @@ export const integerFormatter = new Intl.NumberFormat("en-HK", { | |||||
| }) | }) | ||||
| export const INPUT_DATE_FORMAT = "YYYY-MM-DD"; | |||||
| export const OUTPUT_DATE_FORMAT = "YYYY/MM/DD"; | |||||
| export const OUTPUT_TIME_FORMAT = "HH:mm:ss"; | |||||
| export const stockInLineStatusMap: { [status: string]: number } = { | export const stockInLineStatusMap: { [status: string]: number } = { | ||||
| "draft": 0, | "draft": 0, | ||||
| "pending": 1, | "pending": 1, | ||||
| @@ -36,8 +36,10 @@ import { | |||||
| } from "@mui/x-data-grid"; | } from "@mui/x-data-grid"; | ||||
| import { | import { | ||||
| checkPolAndCompletePo, | checkPolAndCompletePo, | ||||
| fetchPoInClient, | |||||
| fetchStockInLineInfo, | fetchStockInLineInfo, | ||||
| PurchaseQcResult, | PurchaseQcResult, | ||||
| startPo, | |||||
| testFetch, | testFetch, | ||||
| } from "@/app/api/po/actions"; | } from "@/app/api/po/actions"; | ||||
| import { | import { | ||||
| @@ -88,14 +90,46 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
| console.log(cameras); | console.log(cameras); | ||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const apiRef = useGridApiRef(); | const apiRef = useGridApiRef(); | ||||
| const [rows, setRows] = useState<PurchaseOrderLine[]>(po.pol || []); | |||||
| const [purchaseOrder, setPurchaseOrder] = useState({ ...po }); | |||||
| const [rows, setRows] = useState<PurchaseOrderLine[]>( | |||||
| purchaseOrder.pol || [] | |||||
| ); | |||||
| const params = useSearchParams(); | const params = useSearchParams(); | ||||
| const [currPoStatus, setCurrPoStatus] = useState(purchaseOrder.status); | |||||
| const handleComplete = useCallback(async () => { | |||||
| const checkRes = await checkPolAndCompletePo(purchaseOrder.id); | |||||
| console.log(checkRes); | |||||
| const newPo = await fetchPoInClient(purchaseOrder.id); | |||||
| setPurchaseOrder(newPo); | |||||
| }, [checkPolAndCompletePo, fetchPoInClient]); | |||||
| const handleStartPo = useCallback(async () => { | |||||
| const startRes = await startPo(purchaseOrder.id); | |||||
| console.log(startRes); | |||||
| const newPo = await fetchPoInClient(purchaseOrder.id); | |||||
| setPurchaseOrder(newPo); | |||||
| }, [startPo, fetchPoInClient]); | |||||
| useEffect(() => { | |||||
| setRows(purchaseOrder.pol || []); | |||||
| }, [purchaseOrder]); | |||||
| function Row(props: { row: PurchaseOrderLine }) { | function Row(props: { row: PurchaseOrderLine }) { | ||||
| const { row } = props; | const { row } = props; | ||||
| const [open, setOpen] = useState(false); | const [open, setOpen] = useState(false); | ||||
| const [processedQty, setProcessedQty] = useState(row.processed); | const [processedQty, setProcessedQty] = useState(row.processed); | ||||
| const [currStatus, setCurrStatus] = useState(row.status); | const [currStatus, setCurrStatus] = useState(row.status); | ||||
| const [stockInLine, setStockInLine] = useState(row.stockInLine); | |||||
| const totalWeight = useMemo( | |||||
| () => calculateWeight(row.qty, row.uom), | |||||
| [calculateWeight] | |||||
| ); | |||||
| const weightUnit = useMemo( | |||||
| () => returnWeightUnit(row.uom), | |||||
| [returnWeightUnit] | |||||
| ); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| if (processedQty === row.qty) { | if (processedQty === row.qty) { | ||||
| setCurrStatus("completed".toUpperCase()); | setCurrStatus("completed".toUpperCase()); | ||||
| @@ -106,20 +140,12 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
| } | } | ||||
| }, [processedQty]); | }, [processedQty]); | ||||
| const totalWeight = useMemo( | |||||
| () => calculateWeight(row.qty, row.uom), | |||||
| [calculateWeight] | |||||
| ); | |||||
| const weightUnit = useMemo( | |||||
| () => returnWeightUnit(row.uom), | |||||
| [returnWeightUnit] | |||||
| ); | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| <TableRow sx={{ "& > *": { borderBottom: "unset" }, color: "black" }}> | <TableRow sx={{ "& > *": { borderBottom: "unset" }, color: "black" }}> | ||||
| <TableCell> | <TableCell> | ||||
| <IconButton | <IconButton | ||||
| disabled={purchaseOrder.status.toLowerCase() === "pending"} | |||||
| aria-label="expand row" | aria-label="expand row" | ||||
| size="small" | size="small" | ||||
| onClick={() => setOpen(!open)} | onClick={() => setOpen(!open)} | ||||
| @@ -152,9 +178,10 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
| <PoInputGrid | <PoInputGrid | ||||
| qc={qc} | qc={qc} | ||||
| setRows={setRows} | setRows={setRows} | ||||
| stockInLine={stockInLine} | |||||
| setStockInLine={setStockInLine} | |||||
| setProcessedQty={setProcessedQty} | setProcessedQty={setProcessedQty} | ||||
| itemDetail={row} | itemDetail={row} | ||||
| stockInLine={row.stockInLine} | |||||
| warehouse={warehouse} | warehouse={warehouse} | ||||
| /> | /> | ||||
| </Box> | </Box> | ||||
| @@ -199,20 +226,6 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
| setPutAwayOpen(true); | setPutAwayOpen(true); | ||||
| }, []); | }, []); | ||||
| const handleComplete = useCallback(async () => { | |||||
| const res = await checkPolAndCompletePo(po.id) | |||||
| if (res.type === "completed") { | |||||
| // toast.success(res.message) | |||||
| console.log(res) | |||||
| return | |||||
| } | |||||
| if (res.type === "receiving") { | |||||
| // toast.error(res.message) | |||||
| console.log(res) | |||||
| return | |||||
| } | |||||
| }, [checkPolAndCompletePo]); | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| <Stack | <Stack | ||||
| @@ -220,13 +233,26 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
| // component="form" | // component="form" | ||||
| // onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)} | // onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)} | ||||
| > | > | ||||
| <Grid container xs={12} justifyContent="space-between"> | |||||
| <Grid container xs={12} justifyContent="start"> | |||||
| <Grid item> | <Grid item> | ||||
| <Typography mb={2} variant="h4"> | <Typography mb={2} variant="h4"> | ||||
| {po.code} | |||||
| {/* {purchaseOrder.code} - {currPoStatus} */} | |||||
| {purchaseOrder.code} - {purchaseOrder.status} | |||||
| </Typography> | </Typography> | ||||
| </Grid> | </Grid> | ||||
| </Grid> | </Grid> | ||||
| <Grid container xs={12} justifyContent="start"> | |||||
| {purchaseOrder.status.toLowerCase() === "pending" && ( | |||||
| <Grid item> | |||||
| <Button onClick={handleStartPo}>Start</Button> | |||||
| </Grid> | |||||
| )} | |||||
| {purchaseOrder.status.toLowerCase() === "receiving" && ( | |||||
| <Grid item> | |||||
| <Button onClick={handleComplete}>Complete</Button> | |||||
| </Grid> | |||||
| )} | |||||
| </Grid> | |||||
| {/* <Grid container xs={12} justifyContent="space-between"> | {/* <Grid container xs={12} justifyContent="space-between"> | ||||
| <Button onClick={handleComplete}>Complete</Button> | <Button onClick={handleComplete}>Complete</Button> | ||||
| </Grid> */} | </Grid> */} | ||||
| @@ -242,9 +268,15 @@ const PoDetail: React.FC<Props> = ({ po, qc, warehouse }) => { | |||||
| </Tabs> | </Tabs> | ||||
| </Grid> | </Grid> | ||||
| {/* <Grid item xs={4}> */} | {/* <Grid item xs={4}> */} | ||||
| {/* scanner */} | |||||
| {/* scanner */} | |||||
| {/* </Grid> */} | {/* </Grid> */} | ||||
| <Grid item xs={4} display="flex" justifyContent="end" alignItems="end"> | |||||
| <Grid | |||||
| item | |||||
| xs={4} | |||||
| display="flex" | |||||
| justifyContent="end" | |||||
| alignItems="end" | |||||
| > | |||||
| <QrModal | <QrModal | ||||
| open={isOpenScanner} | open={isOpenScanner} | ||||
| onClose={onCloseScanner} | onClose={onCloseScanner} | ||||
| @@ -63,6 +63,7 @@ interface ResultWithId { | |||||
| interface Props { | interface Props { | ||||
| qc: QcItemWithChecks[]; | qc: QcItemWithChecks[]; | ||||
| setRows: Dispatch<SetStateAction<PurchaseOrderLine[]>>; | setRows: Dispatch<SetStateAction<PurchaseOrderLine[]>>; | ||||
| setStockInLine: Dispatch<SetStateAction<StockInLine[]>>; | |||||
| setProcessedQty: Dispatch<SetStateAction<number>>; | setProcessedQty: Dispatch<SetStateAction<number>>; | ||||
| itemDetail: PurchaseOrderLine; | itemDetail: PurchaseOrderLine; | ||||
| stockInLine: StockInLine[]; | stockInLine: StockInLine[]; | ||||
| @@ -100,6 +101,7 @@ class ProcessRowUpdateError extends Error { | |||||
| function PoInputGrid({ | function PoInputGrid({ | ||||
| qc, | qc, | ||||
| setRows, | setRows, | ||||
| setStockInLine, | |||||
| setProcessedQty, | setProcessedQty, | ||||
| itemDetail, | itemDetail, | ||||
| stockInLine, | stockInLine, | ||||
| @@ -122,6 +124,7 @@ function PoInputGrid({ | |||||
| const [escalOpen, setEscalOpen] = useState(false); | const [escalOpen, setEscalOpen] = useState(false); | ||||
| const [stockInOpen, setStockInOpen] = useState(false); | const [stockInOpen, setStockInOpen] = useState(false); | ||||
| const [putAwayOpen, setPutAwayOpen] = useState(false); | const [putAwayOpen, setPutAwayOpen] = useState(false); | ||||
| const [btnIsLoading, setBtnIsLoading] = useState(false); | |||||
| const [currQty, setCurrQty] = useState(() => { | const [currQty, setCurrQty] = useState(() => { | ||||
| const total = entries.reduce( | const total = entries.reduce( | ||||
| (acc, curr) => acc + (curr.acceptedQty || 0), | (acc, curr) => acc + (curr.acceptedQty || 0), | ||||
| @@ -147,17 +150,14 @@ function PoInputGrid({ | |||||
| ); | ); | ||||
| const handleStart = useCallback( | const handleStart = useCallback( | ||||
| (id: GridRowId, params: any) => () => { | (id: GridRowId, params: any) => () => { | ||||
| setBtnIsLoading(true); | |||||
| setRowModesModel((prev) => ({ | setRowModesModel((prev) => ({ | ||||
| ...prev, | ...prev, | ||||
| [id]: { mode: GridRowModes.View }, | [id]: { mode: GridRowModes.View }, | ||||
| })); | })); | ||||
| setTimeout(async () => { | setTimeout(async () => { | ||||
| // post stock in line | // post stock in line | ||||
| console.log("delayed"); | |||||
| console.log(params); | |||||
| const oldId = params.row.id; | const oldId = params.row.id; | ||||
| console.log(oldId); | |||||
| console.log(params.row); | |||||
| const postData = { | const postData = { | ||||
| itemId: params.row.itemId, | itemId: params.row.itemId, | ||||
| itemNo: params.row.itemNo, | itemNo: params.row.itemNo, | ||||
| @@ -171,6 +171,13 @@ function PoInputGrid({ | |||||
| setEntries((prev) => | setEntries((prev) => | ||||
| prev.map((p) => (p.id === oldId ? (res.entity as StockInLine) : p)) | 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 | // do post directly to test | ||||
| // openStartModal(); | // openStartModal(); | ||||
| }, 200); | }, 200); | ||||
| @@ -183,6 +190,7 @@ function PoInputGrid({ | |||||
| const handleQC = useCallback( | const handleQC = useCallback( | ||||
| (id: GridRowId, params: any) => async () => { | (id: GridRowId, params: any) => async () => { | ||||
| setBtnIsLoading(true); | |||||
| setRowModesModel((prev) => ({ | setRowModesModel((prev) => ({ | ||||
| ...prev, | ...prev, | ||||
| [id]: { mode: GridRowModes.View }, | [id]: { mode: GridRowModes.View }, | ||||
| @@ -199,12 +207,14 @@ function PoInputGrid({ | |||||
| // open qc modal | // open qc modal | ||||
| console.log("delayed"); | console.log("delayed"); | ||||
| openQcModal(); | openQcModal(); | ||||
| setBtnIsLoading(false); | |||||
| }, 200); | }, 200); | ||||
| }, | }, | ||||
| [fetchQcDefaultValue] | [fetchQcDefaultValue] | ||||
| ); | ); | ||||
| const handleEscalation = useCallback( | const handleEscalation = useCallback( | ||||
| (id: GridRowId, params: any) => () => { | (id: GridRowId, params: any) => () => { | ||||
| // setBtnIsLoading(true); | |||||
| setRowModesModel((prev) => ({ | setRowModesModel((prev) => ({ | ||||
| ...prev, | ...prev, | ||||
| [id]: { mode: GridRowModes.View }, | [id]: { mode: GridRowModes.View }, | ||||
| @@ -214,12 +224,14 @@ function PoInputGrid({ | |||||
| // open qc modal | // open qc modal | ||||
| console.log("delayed"); | console.log("delayed"); | ||||
| openEscalationModal(); | openEscalationModal(); | ||||
| // setBtnIsLoading(false); | |||||
| }, 200); | }, 200); | ||||
| }, | }, | ||||
| [] | [] | ||||
| ); | ); | ||||
| const handleStockIn = useCallback( | const handleStockIn = useCallback( | ||||
| (id: GridRowId, params: any) => () => { | (id: GridRowId, params: any) => () => { | ||||
| // setBtnIsLoading(true); | |||||
| setRowModesModel((prev) => ({ | setRowModesModel((prev) => ({ | ||||
| ...prev, | ...prev, | ||||
| [id]: { mode: GridRowModes.View }, | [id]: { mode: GridRowModes.View }, | ||||
| @@ -231,6 +243,7 @@ function PoInputGrid({ | |||||
| // return the record with its status as pending | // return the record with its status as pending | ||||
| // update layout | // update layout | ||||
| console.log("delayed"); | console.log("delayed"); | ||||
| // setBtnIsLoading(false); | |||||
| }, 200); | }, 200); | ||||
| }, | }, | ||||
| [] | [] | ||||
| @@ -238,6 +251,7 @@ function PoInputGrid({ | |||||
| const handlePutAway = useCallback( | const handlePutAway = useCallback( | ||||
| (id: GridRowId, params: any) => () => { | (id: GridRowId, params: any) => () => { | ||||
| // setBtnIsLoading(true); | |||||
| setRowModesModel((prev) => ({ | setRowModesModel((prev) => ({ | ||||
| ...prev, | ...prev, | ||||
| [id]: { mode: GridRowModes.View }, | [id]: { mode: GridRowModes.View }, | ||||
| @@ -249,6 +263,7 @@ function PoInputGrid({ | |||||
| // return the record with its status as pending | // return the record with its status as pending | ||||
| // update layout | // update layout | ||||
| console.log("delayed"); | console.log("delayed"); | ||||
| // setBtnIsLoading(false); | |||||
| }, 200); | }, 200); | ||||
| }, | }, | ||||
| [] | [] | ||||
| @@ -256,6 +271,7 @@ function PoInputGrid({ | |||||
| const printQrcode = useCallback( | const printQrcode = useCallback( | ||||
| async (row: any) => { | async (row: any) => { | ||||
| setBtnIsLoading(true); | |||||
| console.log(row.id); | console.log(row.id); | ||||
| const postData = { stockInLineIds: [row.id] }; | const postData = { stockInLineIds: [row.id] }; | ||||
| // const postData = { stockInLineIds: [42,43,44] }; | // const postData = { stockInLineIds: [42,43,44] }; | ||||
| @@ -264,6 +280,7 @@ function PoInputGrid({ | |||||
| console.log(response); | console.log(response); | ||||
| downloadFile(new Uint8Array(response.blobValue), response.filename!!); | downloadFile(new Uint8Array(response.blobValue), response.filename!!); | ||||
| } | } | ||||
| setBtnIsLoading(false); | |||||
| }, | }, | ||||
| [fetchPoQrcode, downloadFile] | [fetchPoQrcode, downloadFile] | ||||
| ); | ); | ||||
| @@ -319,11 +336,11 @@ function PoInputGrid({ | |||||
| () => [ | () => [ | ||||
| { | { | ||||
| field: "itemNo", | field: "itemNo", | ||||
| flex: 0.8, | |||||
| flex: 0.4, | |||||
| }, | }, | ||||
| { | { | ||||
| field: "itemName", | field: "itemName", | ||||
| flex: 1, | |||||
| flex: 0.6, | |||||
| }, | }, | ||||
| { | { | ||||
| field: "acceptedQty", | field: "acceptedQty", | ||||
| @@ -363,7 +380,7 @@ function PoInputGrid({ | |||||
| field: "actions", | field: "actions", | ||||
| type: "actions", | type: "actions", | ||||
| headerName: "start | qc | escalation | stock in | putaway | delete", | headerName: "start | qc | escalation | stock in | putaway | delete", | ||||
| flex: 1, | |||||
| flex: 1.5, | |||||
| cellClassName: "actions", | cellClassName: "actions", | ||||
| getActions: (params) => { | getActions: (params) => { | ||||
| console.log(params.row.status); | console.log(params.row.status); | ||||
| @@ -376,7 +393,7 @@ function PoInputGrid({ | |||||
| color: "primary.main", | color: "primary.main", | ||||
| // marginRight: 1, | // marginRight: 1, | ||||
| }} | }} | ||||
| disabled={!(stockInLineStatusMap[status] === 0)} | |||||
| disabled={btnIsLoading || !(stockInLineStatusMap[status] === 0)} | |||||
| // set _isNew to false after posting | // set _isNew to false after posting | ||||
| // or check status | // or check status | ||||
| onClick={handleStart(params.row.id, params)} | onClick={handleStart(params.row.id, params)} | ||||
| @@ -390,7 +407,7 @@ function PoInputGrid({ | |||||
| color: "primary.main", | color: "primary.main", | ||||
| // marginRight: 1, | // marginRight: 1, | ||||
| }} | }} | ||||
| disabled={stockInLineStatusMap[status] < 1} | |||||
| disabled={btnIsLoading || stockInLineStatusMap[status] < 1} | |||||
| // set _isNew to false after posting | // set _isNew to false after posting | ||||
| // or check status | // or check status | ||||
| onClick={handleQC(params.row.id, params)} | onClick={handleQC(params.row.id, params)} | ||||
| @@ -405,8 +422,9 @@ function PoInputGrid({ | |||||
| // marginRight: 1, | // marginRight: 1, | ||||
| }} | }} | ||||
| disabled={ | disabled={ | ||||
| btnIsLoading || | |||||
| stockInLineStatusMap[status] <= 0 || | stockInLineStatusMap[status] <= 0 || | ||||
| stockInLineStatusMap[status] >= 4 | |||||
| stockInLineStatusMap[status] >= 5 | |||||
| } | } | ||||
| // set _isNew to false after posting | // set _isNew to false after posting | ||||
| // or check status | // or check status | ||||
| @@ -421,7 +439,11 @@ function PoInputGrid({ | |||||
| color: "primary.main", | color: "primary.main", | ||||
| // marginRight: 1, | // marginRight: 1, | ||||
| }} | }} | ||||
| disabled={stockInLineStatusMap[status] <= 2 || stockInLineStatusMap[status] >= 7} | |||||
| disabled={ | |||||
| btnIsLoading || | |||||
| stockInLineStatusMap[status] <= 2 || | |||||
| stockInLineStatusMap[status] >= 7 | |||||
| } | |||||
| // set _isNew to false after posting | // set _isNew to false after posting | ||||
| // or check status | // or check status | ||||
| onClick={handleStockIn(params.row.id, params)} | onClick={handleStockIn(params.row.id, params)} | ||||
| @@ -435,7 +457,7 @@ function PoInputGrid({ | |||||
| color: "primary.main", | color: "primary.main", | ||||
| // marginRight: 1, | // marginRight: 1, | ||||
| }} | }} | ||||
| disabled={stockInLineStatusMap[status] < 7} | |||||
| disabled={btnIsLoading || stockInLineStatusMap[status] < 7} | |||||
| // set _isNew to false after posting | // set _isNew to false after posting | ||||
| // or check status | // or check status | ||||
| onClick={handlePutAway(params.row.id, params)} | onClick={handlePutAway(params.row.id, params)} | ||||
| @@ -449,7 +471,7 @@ function PoInputGrid({ | |||||
| color: "primary.main", | color: "primary.main", | ||||
| // marginRight: 1, | // marginRight: 1, | ||||
| }} | }} | ||||
| disabled={stockInLineStatusMap[status] !== 8} | |||||
| disabled={btnIsLoading || stockInLineStatusMap[status] !== 8} | |||||
| // set _isNew to false after posting | // set _isNew to false after posting | ||||
| // or check status | // or check status | ||||
| onClick={handleQrCode(params.row.id, params)} | onClick={handleQrCode(params.row.id, params)} | ||||
| @@ -462,7 +484,7 @@ function PoInputGrid({ | |||||
| sx={{ | sx={{ | ||||
| color: "error.main", | color: "error.main", | ||||
| }} | }} | ||||
| disabled={stockInLineStatusMap[status] !== 0} | |||||
| disabled={btnIsLoading || stockInLineStatusMap[status] !== 0} | |||||
| // disabled={Boolean(params.row.status)} | // disabled={Boolean(params.row.status)} | ||||
| onClick={handleDelete(params.row.id)} | onClick={handleDelete(params.row.id)} | ||||
| color="inherit" | color="inherit" | ||||
| @@ -472,7 +494,7 @@ function PoInputGrid({ | |||||
| }, | }, | ||||
| }, | }, | ||||
| ], | ], | ||||
| [] | |||||
| [btnIsLoading] | |||||
| ); | ); | ||||
| const addRow = useCallback(() => { | const addRow = useCallback(() => { | ||||
| @@ -533,6 +555,8 @@ function PoInputGrid({ | |||||
| const newEntries = entries.map((e) => | const newEntries = entries.map((e) => | ||||
| getRowId(e) === getRowId(originalRow) ? rowToSave : e | getRowId(e) === getRowId(originalRow) ? rowToSave : e | ||||
| ); | ); | ||||
| setStockInLine(newEntries as StockInLine[]); | |||||
| console.log("triggered"); | |||||
| setEntries(newEntries); | setEntries(newEntries); | ||||
| //update remaining qty | //update remaining qty | ||||
| const total = newEntries.reduce( | const total = newEntries.reduce( | ||||
| @@ -569,9 +593,7 @@ function PoInputGrid({ | |||||
| </Button> | </Button> | ||||
| </Box> | </Box> | ||||
| ); | ); | ||||
| useEffect(() => { | |||||
| console.log(modalInfo); | |||||
| }, [modalInfo]); | |||||
| return ( | return ( | ||||
| <> | <> | ||||
| <StyledDataGrid | <StyledDataGrid | ||||
| @@ -623,7 +645,9 @@ function PoInputGrid({ | |||||
| <> | <> | ||||
| <PoQcStockInModal | <PoQcStockInModal | ||||
| type={"qc"} | type={"qc"} | ||||
| // setRows={setRows} | |||||
| setEntries={setEntries} | setEntries={setEntries} | ||||
| setStockInLine={setStockInLine} | |||||
| setItemDetail={setModalInfo} | setItemDetail={setModalInfo} | ||||
| qc={qc} | qc={qc} | ||||
| open={qcOpen} | open={qcOpen} | ||||
| @@ -636,7 +660,9 @@ function PoInputGrid({ | |||||
| <> | <> | ||||
| <PoQcStockInModal | <PoQcStockInModal | ||||
| type={"escalation"} | type={"escalation"} | ||||
| // setRows={setRows} | |||||
| setEntries={setEntries} | setEntries={setEntries} | ||||
| setStockInLine={setStockInLine} | |||||
| setItemDetail={setModalInfo} | setItemDetail={setModalInfo} | ||||
| // qc={qc} | // qc={qc} | ||||
| open={escalOpen} | open={escalOpen} | ||||
| @@ -649,7 +675,9 @@ function PoInputGrid({ | |||||
| <> | <> | ||||
| <PoQcStockInModal | <PoQcStockInModal | ||||
| type={"stockIn"} | type={"stockIn"} | ||||
| // setRows={setRows} | |||||
| setEntries={setEntries} | setEntries={setEntries} | ||||
| setStockInLine={setStockInLine} | |||||
| // qc={qc} | // qc={qc} | ||||
| setItemDetail={setModalInfo} | setItemDetail={setModalInfo} | ||||
| open={stockInOpen} | open={stockInOpen} | ||||
| @@ -662,7 +690,9 @@ function PoInputGrid({ | |||||
| <> | <> | ||||
| <PoQcStockInModal | <PoQcStockInModal | ||||
| type={"putaway"} | type={"putaway"} | ||||
| // setRows={setRows} | |||||
| setEntries={setEntries} | setEntries={setEntries} | ||||
| setStockInLine={setStockInLine} | |||||
| setItemDetail={setModalInfo} | setItemDetail={setModalInfo} | ||||
| open={putAwayOpen} | open={putAwayOpen} | ||||
| warehouse={warehouse} | warehouse={warehouse} | ||||
| @@ -22,23 +22,35 @@ import { useTranslation } from "react-i18next"; | |||||
| import QcForm from "./QcForm"; | import QcForm from "./QcForm"; | ||||
| import { QcItemWithChecks } from "@/app/api/qc"; | import { QcItemWithChecks } from "@/app/api/qc"; | ||||
| import { Check, CurrencyYuanRounded, TtyTwoTone } from "@mui/icons-material"; | import { Check, CurrencyYuanRounded, TtyTwoTone } from "@mui/icons-material"; | ||||
| import { StockInLine } from "@/app/api/po"; | |||||
| import { PurchaseOrderLine, StockInLine } from "@/app/api/po"; | |||||
| import { useSearchParams } from "next/navigation"; | import { useSearchParams } from "next/navigation"; | ||||
| import { StockInLineRow } from "./PoInputGrid"; | import { StockInLineRow } from "./PoInputGrid"; | ||||
| import EscalationForm from "./EscalationForm"; | import EscalationForm from "./EscalationForm"; | ||||
| import StockInForm from "./StockInForm"; | import StockInForm from "./StockInForm"; | ||||
| import PutawayForm from "./PutawayForm"; | import PutawayForm from "./PutawayForm"; | ||||
| import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | |||||
| import { | |||||
| INPUT_DATE_FORMAT, | |||||
| stockInLineStatusMap, | |||||
| } from "@/app/utils/formatUtil"; | |||||
| import dayjs from "dayjs"; | import dayjs from "dayjs"; | ||||
| import arraySupport from "dayjs/plugin/arraySupport"; | import arraySupport from "dayjs/plugin/arraySupport"; | ||||
| import { downloadFile } from "@/app/utils/commonUtil"; | |||||
| import { fetchPoQrcode } from "@/app/api/pdf/actions"; | |||||
| dayjs.extend(arraySupport) | |||||
| dayjs.extend(arraySupport); | |||||
| interface CommonProps extends Omit<ModalProps, "children"> { | interface CommonProps extends Omit<ModalProps, "children"> { | ||||
| // setRows: Dispatch<SetStateAction<PurchaseOrderLine[]>>; | |||||
| setEntries?: Dispatch<SetStateAction<StockInLineRow[]>>; | setEntries?: Dispatch<SetStateAction<StockInLineRow[]>>; | ||||
| setStockInLine?: Dispatch<SetStateAction<StockInLine[]>>; | |||||
| itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] }; | itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] }; | ||||
| setItemDetail: Dispatch<SetStateAction<(StockInLine & { | |||||
| warehouseId?: number; | |||||
| }) | undefined>> | |||||
| setItemDetail: Dispatch< | |||||
| SetStateAction< | |||||
| | (StockInLine & { | |||||
| warehouseId?: number; | |||||
| }) | |||||
| | undefined | |||||
| > | |||||
| >; | |||||
| qc?: QcItemWithChecks[]; | qc?: QcItemWithChecks[]; | ||||
| warehouse?: any[]; | warehouse?: any[]; | ||||
| type: "qc" | "stockIn" | "escalation" | "putaway"; | type: "qc" | "stockIn" | "escalation" | "putaway"; | ||||
| @@ -75,7 +87,9 @@ const style = { | |||||
| const PoQcStockInModal: React.FC<Props> = ({ | const PoQcStockInModal: React.FC<Props> = ({ | ||||
| type, | type, | ||||
| // setRows, | |||||
| setEntries, | setEntries, | ||||
| setStockInLine, | |||||
| open, | open, | ||||
| onClose, | onClose, | ||||
| itemDetail, | itemDetail, | ||||
| @@ -86,12 +100,16 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||||
| const [serverError, setServerError] = useState(""); | const [serverError, setServerError] = useState(""); | ||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const params = useSearchParams(); | const params = useSearchParams(); | ||||
| const [btnIsLoading, setBtnIsLoading] = useState(false); | |||||
| console.log(params.get("id")); | console.log(params.get("id")); | ||||
| console.log(itemDetail); | console.log(itemDetail); | ||||
| console.log(itemDetail.qcResult); | console.log(itemDetail.qcResult); | ||||
| const formProps = useForm<ModalFormInput>({ | const formProps = useForm<ModalFormInput>({ | ||||
| defaultValues: { | defaultValues: { | ||||
| ...itemDetail, | ...itemDetail, | ||||
| // receiptDate: itemDetail.receiptDate || dayjs().add(-1, "month").format(INPUT_DATE_FORMAT), | |||||
| // warehouseId: itemDetail.defaultWarehouseId || 0 | |||||
| }, | }, | ||||
| }); | }); | ||||
| // console.log(formProps); | // console.log(formProps); | ||||
| @@ -111,40 +129,95 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||||
| } | } | ||||
| }, [itemDetail]); | }, [itemDetail]); | ||||
| const fix0IndexedDate = useCallback((date: string | number[] | undefined) => { | |||||
| if (Array.isArray(date)) { | |||||
| console.log(date) | |||||
| return dayjs([date[0], date[1] - 1, date[2]]).format("YYYY-MM-DD") | |||||
| } | |||||
| return date | |||||
| }, []) | |||||
| // const fix0IndexedDate = useCallback((date: string | number[] | undefined) => { | |||||
| // if (Array.isArray(date)) { | |||||
| // console.log(date); | |||||
| // return dayjs([date[0], date[1] - 1, date[2]]).format("YYYY-MM-DD"); | |||||
| // } | |||||
| // return date; | |||||
| // }, []); | |||||
| const checkStockIn = useCallback( | |||||
| (data: ModalFormInput): boolean => { | |||||
| let hasErrors = false; | |||||
| if (itemDetail.shelfLife && !data.productionDate && !data.expiryDate) { | |||||
| formProps.setError("productionDate", { | |||||
| message: "Please provide at least one", | |||||
| type: "invalid", | |||||
| }); | |||||
| formProps.setError("expiryDate", { | |||||
| message: "Please provide at least one", | |||||
| type: "invalid", | |||||
| }); | |||||
| hasErrors = true; | |||||
| } | |||||
| if (!itemDetail.shelfLife && !data.expiryDate) { | |||||
| formProps.setError("expiryDate", { | |||||
| message: "Please provide expiry date", | |||||
| type: "invalid", | |||||
| }); | |||||
| hasErrors = true; | |||||
| } | |||||
| if (data.expiryDate && data.expiryDate < data.receiptDate!!) { | |||||
| formProps.setError("expiryDate", { | |||||
| message: "Expired", | |||||
| type: "invalid", | |||||
| }); | |||||
| hasErrors = true; | |||||
| } | |||||
| return hasErrors; | |||||
| }, | |||||
| [itemDetail, formProps] | |||||
| ); | |||||
| const checkPutaway = useCallback( | |||||
| (data: ModalFormInput): boolean => { | |||||
| let hasErrors = false; | |||||
| console.log(data.warehouseId); | |||||
| if (!data.warehouseId || data.warehouseId <= 0) { | |||||
| formProps.setError("warehouseId", { | |||||
| message: "Please provide warehouseId", | |||||
| type: "invalid", | |||||
| }); | |||||
| hasErrors = true; | |||||
| } | |||||
| return hasErrors; | |||||
| }, | |||||
| [itemDetail, formProps] | |||||
| ); | |||||
| const onSubmit = useCallback<SubmitHandler<ModalFormInput & {}>>( | const onSubmit = useCallback<SubmitHandler<ModalFormInput & {}>>( | ||||
| async (data, event) => { | async (data, event) => { | ||||
| formProps.clearErrors(); | |||||
| let hasErrors = false; | let hasErrors = false; | ||||
| setBtnIsLoading(true); | |||||
| console.log(errors); | console.log(errors); | ||||
| console.log(data); | console.log(data); | ||||
| console.log(itemDetail); | console.log(itemDetail); | ||||
| console.log(data.receiptDate) | |||||
| console.log(fix0IndexedDate(data.receiptDate)) | |||||
| // console.log(fix0IndexedDate(data.receiptDate)); | |||||
| try { | try { | ||||
| // add checking | // add checking | ||||
| // const qty = data.sampleRate | |||||
| if (type === "stockIn") { | |||||
| hasErrors = checkStockIn(data) | |||||
| console.log(hasErrors) | |||||
| } | |||||
| if (type === "putaway") { | |||||
| hasErrors = checkPutaway(data); | |||||
| console.log(hasErrors) | |||||
| } | |||||
| //////////////////////// modify this mess later ////////////////////// | //////////////////////// modify this mess later ////////////////////// | ||||
| var productionDate = null; | var productionDate = null; | ||||
| var expiryDate = null; | var expiryDate = null; | ||||
| var receiptDate = null; | var receiptDate = null; | ||||
| var acceptedQty = null; | var acceptedQty = null; | ||||
| if (data.productionDate && data.productionDate.length > 0) { | |||||
| productionDate = fix0IndexedDate(data.productionDate); | |||||
| if (data.productionDate) { | |||||
| productionDate = dayjs(data.productionDate).format(INPUT_DATE_FORMAT); | |||||
| } | } | ||||
| if (data.expiryDate && data.expiryDate.length > 0) { | |||||
| expiryDate = fix0IndexedDate(data.expiryDate); | |||||
| if (data.expiryDate) { | |||||
| expiryDate = dayjs(data.expiryDate).format(INPUT_DATE_FORMAT); | |||||
| } | } | ||||
| if (data.receiptDate && data.receiptDate.length > 0) { | |||||
| receiptDate = fix0IndexedDate(data.receiptDate); | |||||
| if (data.receiptDate) { | |||||
| receiptDate = dayjs(data.receiptDate).format(INPUT_DATE_FORMAT); | |||||
| } | } | ||||
| // if () | // if () | ||||
| if (data.qcResult) { | if (data.qcResult) { | ||||
| @@ -163,17 +236,20 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||||
| receiptDate: receiptDate, | receiptDate: receiptDate, | ||||
| } as StockInLineEntry & ModalFormInput; | } as StockInLineEntry & ModalFormInput; | ||||
| ////////////////////////////////////////////////////////////////////// | ////////////////////////////////////////////////////////////////////// | ||||
| console.log(args); | |||||
| // return | |||||
| if (hasErrors) { | if (hasErrors) { | ||||
| console.log(args); | |||||
| setServerError(t("An error has occurred. Please try again later.")); | setServerError(t("An error has occurred. Please try again later.")); | ||||
| return false; | |||||
| setBtnIsLoading(false); | |||||
| return; | |||||
| } | } | ||||
| console.log(args); | |||||
| // setBtnIsLoading(false); | |||||
| // return | |||||
| const res = await updateStockInLine(args); | const res = await updateStockInLine(args); | ||||
| if (Boolean(res.id)) { | if (Boolean(res.id)) { | ||||
| // update entries | // update entries | ||||
| const newEntries = res.entity as StockInLine[]; | const newEntries = res.entity as StockInLine[]; | ||||
| console.log(newEntries) | |||||
| console.log(newEntries); | |||||
| if (setEntries) { | if (setEntries) { | ||||
| setEntries((prev) => { | setEntries((prev) => { | ||||
| const updatedEntries = [...prev]; // Create a new array | const updatedEntries = [...prev]; // Create a new array | ||||
| @@ -181,7 +257,24 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||||
| const index = updatedEntries.findIndex((p) => p.id === item.id); | const index = updatedEntries.findIndex((p) => p.id === item.id); | ||||
| if (index !== -1) { | if (index !== -1) { | ||||
| // Update existing item | // Update existing item | ||||
| console.log(item) | |||||
| console.log(item); | |||||
| updatedEntries[index] = item; | |||||
| } else { | |||||
| // Add new item | |||||
| updatedEntries.push(item); | |||||
| } | |||||
| }); | |||||
| return updatedEntries; // Return the new array | |||||
| }); | |||||
| } | |||||
| if (setStockInLine) { | |||||
| setStockInLine((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 | |||||
| console.log(item); | |||||
| updatedEntries[index] = item; | updatedEntries[index] = item; | ||||
| } else { | } else { | ||||
| // Add new item | // Add new item | ||||
| @@ -192,31 +285,56 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||||
| }); | }); | ||||
| } | } | ||||
| // add loading | // add loading | ||||
| setItemDetail(undefined) | |||||
| setBtnIsLoading(false); | |||||
| setItemDetail(undefined); | |||||
| closeHandler({}, "backdropClick"); | closeHandler({}, "backdropClick"); | ||||
| } | } | ||||
| console.log(res); | console.log(res); | ||||
| // if (res) | // if (res) | ||||
| } catch (e) { | } catch (e) { | ||||
| // server error | // server error | ||||
| setBtnIsLoading(false); | |||||
| setServerError(t("An error has occurred. Please try again later.")); | setServerError(t("An error has occurred. Please try again later.")); | ||||
| console.log(e); | console.log(e); | ||||
| } | } | ||||
| }, | }, | ||||
| [t, itemDetail] | |||||
| [t, itemDetail, checkStockIn, checkPutaway] | |||||
| ); | ); | ||||
| const printQrcode = useCallback(async () => { | |||||
| setBtnIsLoading(true); | |||||
| const postData = { stockInLineIds: [itemDetail.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); | |||||
| }, [itemDetail, fetchPoQrcode, downloadFile]); | |||||
| const renderSubmitButton = useMemo((): Boolean => { | const renderSubmitButton = useMemo((): Boolean => { | ||||
| if (itemDetail) { | if (itemDetail) { | ||||
| const status = itemDetail.status; | const status = itemDetail.status; | ||||
| console.log(status); | console.log(status); | ||||
| switch (type) { | switch (type) { | ||||
| case "qc": | case "qc": | ||||
| return stockInLineStatusMap[status] >= 1 && stockInLineStatusMap[status] <= 2; | |||||
| return ( | |||||
| stockInLineStatusMap[status] >= 1 && | |||||
| stockInLineStatusMap[status] <= 2 | |||||
| ); | |||||
| case "escalation": | case "escalation": | ||||
| return stockInLineStatusMap[status] === 1 || stockInLineStatusMap[status] >= 3 || stockInLineStatusMap[status] <= 5; | |||||
| return ( | |||||
| stockInLineStatusMap[status] === 1 || | |||||
| stockInLineStatusMap[status] >= 3 || | |||||
| stockInLineStatusMap[status] <= 5 | |||||
| ); | |||||
| case "stockIn": | case "stockIn": | ||||
| return stockInLineStatusMap[status] >= 3 && stockInLineStatusMap[status] <= 6; | |||||
| return ( | |||||
| stockInLineStatusMap[status] >= 3 && | |||||
| stockInLineStatusMap[status] <= 6 | |||||
| ); | |||||
| case "putaway": | case "putaway": | ||||
| return stockInLineStatusMap[status] === 7; | return stockInLineStatusMap[status] === 7; | ||||
| default: | default: | ||||
| @@ -248,19 +366,30 @@ const PoQcStockInModal: React.FC<Props> = ({ | |||||
| {itemDetail !== undefined && type === "putaway" && ( | {itemDetail !== undefined && type === "putaway" && ( | ||||
| <PutawayForm itemDetail={itemDetail} warehouse={warehouse!!} /> | <PutawayForm itemDetail={itemDetail} warehouse={warehouse!!} /> | ||||
| )} | )} | ||||
| {renderSubmitButton ? ( | |||||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | |||||
| <Stack direction="row" justifyContent="flex-end" gap={1}> | |||||
| {renderSubmitButton ? ( | |||||
| <Button | <Button | ||||
| name="submit" | name="submit" | ||||
| variant="contained" | variant="contained" | ||||
| startIcon={<Check />} | startIcon={<Check />} | ||||
| type="submit" | type="submit" | ||||
| // disabled={submitDisabled} | |||||
| disabled={btnIsLoading} | |||||
| > | > | ||||
| {t("submit")} | {t("submit")} | ||||
| </Button> | </Button> | ||||
| </Stack> | |||||
| ) : undefined} | |||||
| ) : undefined} | |||||
| {itemDetail !== undefined && type === "putaway" && ( | |||||
| <Button | |||||
| name="print" | |||||
| variant="contained" | |||||
| // startIcon={<Check />} | |||||
| onClick={printQrcode} | |||||
| disabled={btnIsLoading} | |||||
| > | |||||
| {t("print")} | |||||
| </Button> | |||||
| )} | |||||
| </Stack> | |||||
| </Box> | </Box> | ||||
| </Modal> | </Modal> | ||||
| </FormProvider> | </FormProvider> | ||||
| @@ -16,7 +16,7 @@ import { | |||||
| Tooltip, | Tooltip, | ||||
| Typography, | Typography, | ||||
| } from "@mui/material"; | } from "@mui/material"; | ||||
| import { useFormContext } from "react-hook-form"; | |||||
| import { Controller, useFormContext } from "react-hook-form"; | |||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import StyledDataGrid from "../StyledDataGrid"; | import StyledDataGrid from "../StyledDataGrid"; | ||||
| import { useCallback, useEffect, useMemo, useState } from "react"; | import { useCallback, useEffect, useMemo, useState } from "react"; | ||||
| @@ -40,7 +40,9 @@ import { WarehouseResult } from "@/app/api/warehouse"; | |||||
| import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | import { stockInLineStatusMap } from "@/app/utils/formatUtil"; | ||||
| import { QRCodeSVG } from "qrcode.react"; | import { QRCodeSVG } from "qrcode.react"; | ||||
| import { QrCode } from "../QrCode"; | import { QrCode } from "../QrCode"; | ||||
| import ReactQrCodeScanner, { ScannerConfig } from "../ReactQrCodeScanner/ReactQrCodeScanner"; | |||||
| import ReactQrCodeScanner, { | |||||
| ScannerConfig, | |||||
| } from "../ReactQrCodeScanner/ReactQrCodeScanner"; | |||||
| import { QrCodeInfo } from "@/app/api/qrcode"; | import { QrCodeInfo } from "@/app/api/qrcode"; | ||||
| interface Props { | interface Props { | ||||
| @@ -84,19 +86,24 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||||
| clearErrors, | clearErrors, | ||||
| } = useFormContext<PutawayInput>(); | } = useFormContext<PutawayInput>(); | ||||
| console.log(itemDetail); | console.log(itemDetail); | ||||
| const [recordQty, setRecordQty] = useState(0); | |||||
| const [warehouseId, setWarehouseId] = useState(0); | |||||
| // const [recordQty, setRecordQty] = useState(0); | |||||
| const [warehouseId, setWarehouseId] = useState(itemDetail.defaultWarehouseId); | |||||
| const filteredWarehouse = useMemo(() => { | const filteredWarehouse = useMemo(() => { | ||||
| // do filtering here if any | // do filtering here if any | ||||
| return warehouse; | return warehouse; | ||||
| }, []); | }, []); | ||||
| const defaultOption = { | |||||
| value: 0, // think think sin | |||||
| label: t("Select warehouse"), | |||||
| group: "default", | |||||
| } | |||||
| const options = useMemo(() => { | const options = useMemo(() => { | ||||
| return [ | return [ | ||||
| { | |||||
| value: -1, // think think sin | |||||
| label: t("Select warehouse"), | |||||
| group: "default", | |||||
| }, | |||||
| // { | |||||
| // value: 0, // think think sin | |||||
| // label: t("Select warehouse"), | |||||
| // group: "default", | |||||
| // }, | |||||
| ...filteredWarehouse.map((w) => ({ | ...filteredWarehouse.map((w) => ({ | ||||
| value: w.id, | value: w.id, | ||||
| label: `${w.code} - ${w.name}`, | label: `${w.code} - ${w.name}`, | ||||
| @@ -107,8 +114,8 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||||
| const currentValue = | const currentValue = | ||||
| warehouseId > 0 | warehouseId > 0 | ||||
| ? options.find((o) => o.value === warehouseId) | ? options.find((o) => o.value === warehouseId) | ||||
| : options.find((o) => o.value === getValues("warehouseId")) || options[0]; | |||||
| : options.find((o) => o.value === getValues("warehouseId")) || defaultOption; | |||||
| const onChange = useCallback( | const onChange = useCallback( | ||||
| ( | ( | ||||
| event: React.SyntheticEvent, | event: React.SyntheticEvent, | ||||
| @@ -119,19 +126,47 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||||
| group: string; | group: string; | ||||
| }; | }; | ||||
| console.log(singleNewVal); | console.log(singleNewVal); | ||||
| setValue("warehouseId", singleNewVal.value); | |||||
| console.log("onChange"); | |||||
| // setValue("warehouseId", singleNewVal.value); | |||||
| setWarehouseId(singleNewVal.value); | setWarehouseId(singleNewVal.value); | ||||
| }, | }, | ||||
| [] | [] | ||||
| ); | ); | ||||
| // const accQty = watch("acceptedQty"); | |||||
| // const validateForm = useCallback(() => { | |||||
| // console.log(accQty); | |||||
| // if (accQty > itemDetail.acceptedQty) { | |||||
| // setError("acceptedQty", { | |||||
| // message: `acceptedQty must not greater than ${itemDetail.acceptedQty}`, | |||||
| // type: "required", | |||||
| // }); | |||||
| // } | |||||
| // if (accQty < 1) { | |||||
| // setError("acceptedQty", { | |||||
| // message: `minimal value is 1`, | |||||
| // type: "required", | |||||
| // }); | |||||
| // } | |||||
| // if (isNaN(accQty)) { | |||||
| // setError("acceptedQty", { | |||||
| // message: `value must be a number`, | |||||
| // type: "required", | |||||
| // }); | |||||
| // } | |||||
| // }, [accQty]); | |||||
| // useEffect(() => { | |||||
| // clearErrors(); | |||||
| // validateForm(); | |||||
| // }, [validateForm]); | |||||
| const qrContent = useMemo( | const qrContent = useMemo( | ||||
| () => ({ | () => ({ | ||||
| stockInLineId: itemDetail.id, | stockInLineId: itemDetail.id, | ||||
| itemId: itemDetail.itemId, | itemId: itemDetail.itemId, | ||||
| lotNo: itemDetail.lotNo, | lotNo: itemDetail.lotNo, | ||||
| // warehouseId: 1 // for testing | |||||
| // warehouseId: 2 // for testing | |||||
| // expiryDate: itemDetail.expiryDate, | // expiryDate: itemDetail.expiryDate, | ||||
| // productionDate: itemDetail.productionDate, | // productionDate: itemDetail.productionDate, | ||||
| // supplier: itemDetail.supplier, | // supplier: itemDetail.supplier, | ||||
| @@ -140,7 +175,7 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||||
| [itemDetail] | [itemDetail] | ||||
| ); | ); | ||||
| const [isOpenScanner, setOpenScanner] = useState(false); | const [isOpenScanner, setOpenScanner] = useState(false); | ||||
| const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>( | const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>( | ||||
| (...args) => { | (...args) => { | ||||
| setOpenScanner(false); | setOpenScanner(false); | ||||
| @@ -158,23 +193,33 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||||
| const scannerConfig = useMemo<ScannerConfig>( | const scannerConfig = useMemo<ScannerConfig>( | ||||
| () => ({ | () => ({ | ||||
| onUpdate: (err, result) => { | onUpdate: (err, result) => { | ||||
| console.log(result); | |||||
| console.log(Boolean(result)); | |||||
| if (result) { | if (result) { | ||||
| const data: QrCodeInfo = JSON.parse(result.getText()); | const data: QrCodeInfo = JSON.parse(result.getText()); | ||||
| console.log(data); | console.log(data); | ||||
| if (data.warehouseId) { | if (data.warehouseId) { | ||||
| console.log(data.warehouseId); | |||||
| setWarehouseId(data.warehouseId); | setWarehouseId(data.warehouseId); | ||||
| onCloseScanner() | |||||
| onCloseScanner(); | |||||
| } | } | ||||
| } else return; | } else return; | ||||
| }, | }, | ||||
| }), | }), | ||||
| [] | |||||
| [onCloseScanner] | |||||
| ); | ); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| setValue("status", "completed"); | setValue("status", "completed"); | ||||
| }, []); | }, []); | ||||
| useEffect(() => { | |||||
| if (warehouseId > 0) { | |||||
| setValue("warehouseId", warehouseId); | |||||
| clearErrors("warehouseId") | |||||
| } | |||||
| }, [warehouseId]); | |||||
| return ( | return ( | ||||
| <Grid container justifyContent="flex-start" alignItems="flex-start"> | <Grid container justifyContent="flex-start" alignItems="flex-start"> | ||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| @@ -260,11 +305,13 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||||
| disableClearable | disableClearable | ||||
| disabled | disabled | ||||
| fullWidth | fullWidth | ||||
| defaultValue={options.find((o) => o.value === 1)} | |||||
| defaultValue={options.find((o) => o.value === 1)} /// modify this later | |||||
| // onChange={onChange} | // onChange={onChange} | ||||
| getOptionLabel={(option) => option.label} | getOptionLabel={(option) => option.label} | ||||
| options={options} | options={options} | ||||
| renderInput={(params) => <TextField {...params} label="Default Warehouse"/>} | |||||
| renderInput={(params) => ( | |||||
| <TextField {...params} label="Default Warehouse" /> | |||||
| )} | |||||
| /> | /> | ||||
| </FormControl> | </FormControl> | ||||
| </Grid> | </Grid> | ||||
| @@ -278,25 +325,63 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||||
| max: itemDetail.acceptedQty, | max: itemDetail.acceptedQty, | ||||
| valueAsNumber: true, | valueAsNumber: true, | ||||
| })} | })} | ||||
| defaultValue={itemDetail.acceptedQty} | |||||
| // defaultValue={itemDetail.acceptedQty} | |||||
| error={Boolean(errors.acceptedQty)} | error={Boolean(errors.acceptedQty)} | ||||
| helperText={errors.acceptedQty?.message} | helperText={errors.acceptedQty?.message} | ||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={1}> | <Grid item xs={1}> | ||||
| <Button onClick={onOpenScanner}>bind</Button> | |||||
| <Button onClick={onOpenScanner}>bind</Button> | |||||
| </Grid> | </Grid> | ||||
| <Grid item xs={5.5}> | <Grid item xs={5.5}> | ||||
| {/* <Controller | |||||
| control={control} | |||||
| name="warehouseId" | |||||
| render={({ field }) => { | |||||
| console.log(field); | |||||
| return ( | |||||
| <Autocomplete | |||||
| noOptionsText={t("No Warehouse")} | |||||
| disableClearable | |||||
| fullWidth | |||||
| value={options.find((o) => o.value == field.value)} | |||||
| onChange={onChange} | |||||
| getOptionLabel={(option) => option.label} | |||||
| options={options} | |||||
| renderInput={(params) => ( | |||||
| <TextField | |||||
| {...params} | |||||
| label={"Select warehouse"} | |||||
| error={Boolean(errors.warehouseId?.message)} | |||||
| helperText={warehouseHelperText} | |||||
| // helperText={errors.warehouseId?.message} | |||||
| /> | |||||
| )} | |||||
| /> | |||||
| ); | |||||
| }} | |||||
| /> */} | |||||
| <FormControl fullWidth> | <FormControl fullWidth> | ||||
| <Autocomplete | <Autocomplete | ||||
| noOptionsText={t("No Warehouse")} | noOptionsText={t("No Warehouse")} | ||||
| disableClearable | disableClearable | ||||
| fullWidth | fullWidth | ||||
| // value={warehouseId > 0 | |||||
| // ? options.find((o) => o.value === warehouseId) | |||||
| // : undefined} | |||||
| value={currentValue} | value={currentValue} | ||||
| onChange={onChange} | onChange={onChange} | ||||
| getOptionLabel={(option) => option.label} | getOptionLabel={(option) => option.label} | ||||
| options={options} | options={options} | ||||
| renderInput={(params) => <TextField {...params} />} | |||||
| renderInput={(params) => ( | |||||
| <TextField | |||||
| {...params} | |||||
| // label={"Select warehouse"} | |||||
| error={Boolean(errors.warehouseId?.message)} | |||||
| helperText={errors.warehouseId?.message} | |||||
| // helperText={warehouseHelperText} | |||||
| /> | |||||
| )} | |||||
| /> | /> | ||||
| </FormControl> | </FormControl> | ||||
| </Grid> | </Grid> | ||||
| @@ -318,11 +403,11 @@ const PutawayForm: React.FC<Props> = ({ itemDetail, warehouse }) => { | |||||
| <Button onClick={onOpenScanner}>bind</Button> | <Button onClick={onOpenScanner}>bind</Button> | ||||
| </Grid> */} | </Grid> */} | ||||
| <Modal open={isOpenScanner} onClose={closeHandler}> | |||||
| <Box sx={style}> | |||||
| <ReactQrCodeScanner scannerConfig={scannerConfig} /> | |||||
| </Box> | |||||
| </Modal> | |||||
| <Modal open={isOpenScanner} onClose={closeHandler}> | |||||
| <Box sx={style}> | |||||
| <ReactQrCodeScanner scannerConfig={scannerConfig} /> | |||||
| </Box> | |||||
| </Modal> | |||||
| </Grid> | </Grid> | ||||
| ); | ); | ||||
| }; | }; | ||||
| @@ -38,7 +38,7 @@ import { NEXT_PUBLIC_API_URL } from "@/config/api"; | |||||
| import axiosInstance from "@/app/(main)/axios/axiosInstance"; | import axiosInstance from "@/app/(main)/axios/axiosInstance"; | ||||
| interface Props { | interface Props { | ||||
| itemDetail: StockInLine | |||||
| itemDetail: StockInLine; | |||||
| qc: QcItemWithChecks[]; | qc: QcItemWithChecks[]; | ||||
| } | } | ||||
| type EntryError = | type EntryError = | ||||
| @@ -65,9 +65,37 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||||
| clearErrors, | clearErrors, | ||||
| } = useFormContext<PurchaseQCInput>(); | } = useFormContext<PurchaseQCInput>(); | ||||
| console.log(itemDetail); | console.log(itemDetail); | ||||
| console.log(defaultValues); | |||||
| const [recordQty, setRecordQty] = useState(0); | |||||
| console.log(defaultValues); | |||||
| //// validate form | |||||
| const accQty = watch("acceptedQty"); | |||||
| const validateForm = useCallback(() => { | |||||
| console.log(accQty); | |||||
| if (accQty > itemDetail.acceptedQty) { | |||||
| setError("acceptedQty", { | |||||
| message: `acceptedQty must not greater than ${itemDetail.acceptedQty}`, | |||||
| type: "required", | |||||
| }); | |||||
| } | |||||
| if (accQty < 1) { | |||||
| setError("acceptedQty", { | |||||
| message: `minimal value is 1`, | |||||
| type: "required", | |||||
| }); | |||||
| } | |||||
| if (isNaN(accQty)) { | |||||
| setError("acceptedQty", { | |||||
| message: `value must be a number`, | |||||
| type: "required", | |||||
| }); | |||||
| } | |||||
| }, [accQty]); | |||||
| useEffect(() => { | |||||
| clearErrors(); | |||||
| validateForm(); | |||||
| }, [validateForm]); | |||||
| // const [recordQty, setRecordQty] = useState(0); | |||||
| const columns = useMemo<GridColDef[]>( | const columns = useMemo<GridColDef[]>( | ||||
| () => [ | () => [ | ||||
| { | { | ||||
| @@ -105,7 +133,7 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||||
| // id: params.id, | // id: params.id, | ||||
| // field: "type", | // field: "type", | ||||
| // value: "determine1", | // value: "determine1", | ||||
| // }); | |||||
| // }); | |||||
| }} | }} | ||||
| /> | /> | ||||
| ); | ); | ||||
| @@ -144,6 +172,7 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||||
| ], | ], | ||||
| [qc] | [qc] | ||||
| ); | ); | ||||
| /// validate datagrid | |||||
| const validation = useCallback( | const validation = useCallback( | ||||
| (newRow: GridRowModel<PoQcRow>): EntryError => { | (newRow: GridRowModel<PoQcRow>): EntryError => { | ||||
| const error: EntryError = {}; | const error: EntryError = {}; | ||||
| @@ -161,17 +190,17 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||||
| }, | }, | ||||
| [] | [] | ||||
| ); | ); | ||||
| useEffect(() => { | useEffect(() => { | ||||
| console.log(itemDetail) | |||||
| var status = "receiving" | |||||
| console.log(itemDetail); | |||||
| var status = "receiving"; | |||||
| // switch (itemDetail.status) { | // switch (itemDetail.status) { | ||||
| // case 'pending': | // case 'pending': | ||||
| // status = "receiving" | // status = "receiving" | ||||
| // break; | // break; | ||||
| // } | // } | ||||
| setValue("status", status) | |||||
| }, [itemDetail]) | |||||
| setValue("status", status); | |||||
| }, [itemDetail]); | |||||
| return ( | return ( | ||||
| <Grid container justifyContent="flex-start" alignItems="flex-start"> | <Grid container justifyContent="flex-start" alignItems="flex-start"> | ||||
| @@ -187,21 +216,22 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||||
| spacing={2} | spacing={2} | ||||
| sx={{ mt: 0.5 }} | sx={{ mt: 0.5 }} | ||||
| > | > | ||||
| <Grid item xs={12} lg={6}> | |||||
| <Grid item xs={12} lg={12}> | |||||
| <TextField | <TextField | ||||
| label={t("accepted Qty")} | label={t("accepted Qty")} | ||||
| fullWidth | fullWidth | ||||
| // value={itemDetail.acceptedQty} | // value={itemDetail.acceptedQty} | ||||
| {...register("acceptedQty", { | {...register("acceptedQty", { | ||||
| required: "acceptedQty required!", | required: "acceptedQty required!", | ||||
| valueAsNumber: true | |||||
| valueAsNumber: true, | |||||
| max: itemDetail.acceptedQty, | |||||
| })} | })} | ||||
| // disabled | // disabled | ||||
| error={Boolean(errors.acceptedQty)} | error={Boolean(errors.acceptedQty)} | ||||
| helperText={errors.acceptedQty?.message} | helperText={errors.acceptedQty?.message} | ||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={12} lg={6}> | |||||
| {/* <Grid item xs={12} lg={6}> | |||||
| <TextField | <TextField | ||||
| label={t("Total record qty")} | label={t("Total record qty")} | ||||
| fullWidth | fullWidth | ||||
| @@ -213,14 +243,15 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||||
| // error={Boolean(errors.sampleRate)} | // error={Boolean(errors.sampleRate)} | ||||
| // helperText={errors.sampleRate?.message} | // helperText={errors.sampleRate?.message} | ||||
| /> | /> | ||||
| </Grid> | |||||
| </Grid> */} | |||||
| <Grid item xs={12} lg={6}> | <Grid item xs={12} lg={6}> | ||||
| <TextField | <TextField | ||||
| label={t("sampleRate")} | label={t("sampleRate")} | ||||
| fullWidth | fullWidth | ||||
| defaultValue={1} | |||||
| {...register("sampleRate", { | {...register("sampleRate", { | ||||
| required: "sampleRate required!", | required: "sampleRate required!", | ||||
| valueAsNumber: true | |||||
| valueAsNumber: true, | |||||
| })} | })} | ||||
| error={Boolean(errors.sampleRate)} | error={Boolean(errors.sampleRate)} | ||||
| helperText={errors.sampleRate?.message} | helperText={errors.sampleRate?.message} | ||||
| @@ -230,8 +261,10 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||||
| <TextField | <TextField | ||||
| label={t("sampleWeight")} | label={t("sampleWeight")} | ||||
| fullWidth | fullWidth | ||||
| defaultValue={1} | |||||
| {...register("sampleWeight", { | {...register("sampleWeight", { | ||||
| required: "sampleWeight required!", | required: "sampleWeight required!", | ||||
| valueAsNumber: true, | |||||
| })} | })} | ||||
| error={Boolean(errors.sampleWeight)} | error={Boolean(errors.sampleWeight)} | ||||
| helperText={errors.sampleWeight?.message} | helperText={errors.sampleWeight?.message} | ||||
| @@ -241,8 +274,10 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||||
| <TextField | <TextField | ||||
| label={t("totalWeight")} | label={t("totalWeight")} | ||||
| fullWidth | fullWidth | ||||
| defaultValue={1} | |||||
| {...register("totalWeight", { | {...register("totalWeight", { | ||||
| required: "totalWeight required!", | required: "totalWeight required!", | ||||
| valueAsNumber: true, | |||||
| })} | })} | ||||
| error={Boolean(errors.totalWeight)} | error={Boolean(errors.totalWeight)} | ||||
| helperText={errors.totalWeight?.message} | helperText={errors.totalWeight?.message} | ||||
| @@ -263,7 +298,9 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail }) => { | |||||
| _formKey={"qcResult"} | _formKey={"qcResult"} | ||||
| columns={columns} | columns={columns} | ||||
| validateRow={validation} | validateRow={validation} | ||||
| needAdd={itemDetail.status === "qc" || itemDetail.status === "pending"} | |||||
| needAdd={ | |||||
| itemDetail.status === "qc" || itemDetail.status === "pending" | |||||
| } | |||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| </Grid> | </Grid> | ||||
| @@ -1,6 +1,10 @@ | |||||
| "use client"; | "use client"; | ||||
| import { PurchaseQcResult, PurchaseQCInput, StockInInput } from "@/app/api/po/actions"; | |||||
| import { | |||||
| PurchaseQcResult, | |||||
| PurchaseQCInput, | |||||
| StockInInput, | |||||
| } from "@/app/api/po/actions"; | |||||
| import { | import { | ||||
| Box, | Box, | ||||
| Card, | Card, | ||||
| @@ -11,7 +15,7 @@ import { | |||||
| Tooltip, | Tooltip, | ||||
| Typography, | Typography, | ||||
| } from "@mui/material"; | } from "@mui/material"; | ||||
| import { useFormContext } from "react-hook-form"; | |||||
| import { Controller, useFormContext } from "react-hook-form"; | |||||
| import { useTranslation } from "react-i18next"; | import { useTranslation } from "react-i18next"; | ||||
| import StyledDataGrid from "../StyledDataGrid"; | import StyledDataGrid from "../StyledDataGrid"; | ||||
| import { useCallback, useEffect, useMemo } from "react"; | import { useCallback, useEffect, useMemo } from "react"; | ||||
| @@ -31,6 +35,10 @@ import QcSelect from "./QcSelect"; | |||||
| import { QcItemWithChecks } from "@/app/api/qc"; | import { QcItemWithChecks } from "@/app/api/qc"; | ||||
| import { GridEditInputCell } from "@mui/x-data-grid"; | import { GridEditInputCell } from "@mui/x-data-grid"; | ||||
| import { StockInLine } from "@/app/api/po"; | import { StockInLine } from "@/app/api/po"; | ||||
| import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers"; | |||||
| import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; | |||||
| import { INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | |||||
| import dayjs from "dayjs"; | |||||
| // change PurchaseQcResult to stock in entry props | // change PurchaseQcResult to stock in entry props | ||||
| interface Props { | interface Props { | ||||
| itemDetail: StockInLine; | itemDetail: StockInLine; | ||||
| @@ -44,11 +52,14 @@ type EntryError = | |||||
| // type PoQcRow = TableRow<Partial<PurchaseQcResult>, EntryError>; | // type PoQcRow = TableRow<Partial<PurchaseQcResult>, EntryError>; | ||||
| const StockInForm: React.FC<Props> = ({ | |||||
| const StockInForm: React.FC<Props> = ({ | |||||
| // qc, | // qc, | ||||
| itemDetail, | itemDetail, | ||||
| }) => { | |||||
| const { t } = useTranslation(); | |||||
| }) => { | |||||
| const { | |||||
| t, | |||||
| i18n: { language }, | |||||
| } = useTranslation(); | |||||
| const apiRef = useGridApiRef(); | const apiRef = useGridApiRef(); | ||||
| const { | const { | ||||
| register, | register, | ||||
| @@ -62,18 +73,33 @@ const StockInForm: React.FC<Props> = ({ | |||||
| setError, | setError, | ||||
| clearErrors, | clearErrors, | ||||
| } = useFormContext<StockInInput>(); | } = useFormContext<StockInInput>(); | ||||
| console.log(itemDetail) | |||||
| console.log(itemDetail); | |||||
| useEffect(() => { | |||||
| console.log("triggered"); | |||||
| // receiptDate default tdy | |||||
| setValue("receiptDate", dayjs().add(0, "month").format(INPUT_DATE_FORMAT)); | |||||
| setValue("status", "received"); | |||||
| }, []); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| console.log("triggered") | |||||
| setValue("status", "received") | |||||
| }, []) | |||||
| console.log(errors); | |||||
| }, [errors]); | |||||
| const productionDate = watch("productionDate"); | |||||
| const expiryDate = watch("expiryDate"); | |||||
| useEffect(() => { | |||||
| console.log(productionDate) | |||||
| console.log(expiryDate) | |||||
| if (expiryDate) clearErrors() | |||||
| if (productionDate) clearErrors() | |||||
| }, [productionDate, expiryDate]) | |||||
| return ( | return ( | ||||
| <Grid container justifyContent="flex-start" alignItems="flex-start"> | <Grid container justifyContent="flex-start" alignItems="flex-start"> | ||||
| <Grid item xs={12}> | <Grid item xs={12}> | ||||
| <Typography variant="h6" display="block" marginBlockEnd={1}> | <Typography variant="h6" display="block" marginBlockEnd={1}> | ||||
| {t("Qc Detail")} | |||||
| {t("Stock In Detail")} | |||||
| </Typography> | </Typography> | ||||
| </Grid> | </Grid> | ||||
| <Grid | <Grid | ||||
| @@ -95,14 +121,38 @@ const StockInForm: React.FC<Props> = ({ | |||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={4}> | <Grid item xs={4}> | ||||
| <TextField | |||||
| label={t("receiptDate")} | |||||
| fullWidth | |||||
| {...register("receiptDate", { | |||||
| required: "receiptDate required!", | |||||
| })} | |||||
| error={Boolean(errors.receiptDate)} | |||||
| helperText={errors.receiptDate?.message} | |||||
| <Controller | |||||
| control={control} | |||||
| name="receiptDate" | |||||
| rules={{ required: true }} | |||||
| render={({ field }) => { | |||||
| return ( | |||||
| <LocalizationProvider | |||||
| dateAdapter={AdapterDayjs} | |||||
| adapterLocale={`${language}-hk`} | |||||
| > | |||||
| <DatePicker | |||||
| {...field} | |||||
| sx={{ width: "100%" }} | |||||
| label={t("receiptDate")} | |||||
| value={dayjs(watch("receiptDate"))} | |||||
| onChange={(date) => { | |||||
| if (!date) return | |||||
| // setValue("receiptDate", date.format(INPUT_DATE_FORMAT)); | |||||
| field.onChange(date); | |||||
| }} | |||||
| inputRef={field.ref} | |||||
| slotProps={{ | |||||
| textField: { | |||||
| // required: true, | |||||
| error: Boolean(errors.receiptDate?.message), | |||||
| helperText: errors.receiptDate?.message, | |||||
| }, | |||||
| }} | |||||
| /> | |||||
| </LocalizationProvider> | |||||
| ); | |||||
| }} | |||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={4}> | <Grid item xs={4}> | ||||
| @@ -128,25 +178,75 @@ const StockInForm: React.FC<Props> = ({ | |||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={4}> | <Grid item xs={4}> | ||||
| <TextField | |||||
| label={t("productionDate")} | |||||
| fullWidth | |||||
| {...register("productionDate", { | |||||
| // required: "productionDate required!", | |||||
| })} | |||||
| // error={Boolean(errors.productionDate)} | |||||
| // helperText={errors.productionDate?.message} | |||||
| <Controller | |||||
| control={control} | |||||
| name="productionDate" | |||||
| // rules={{ required: !Boolean(expiryDate) }} | |||||
| render={({ field }) => { | |||||
| return ( | |||||
| <LocalizationProvider | |||||
| dateAdapter={AdapterDayjs} | |||||
| adapterLocale={`${language}-hk`} | |||||
| > | |||||
| <DatePicker | |||||
| {...field} | |||||
| sx={{ width: "100%" }} | |||||
| label={t("productionDate")} | |||||
| value={productionDate ? dayjs(productionDate) : undefined} | |||||
| onChange={(date) => { | |||||
| if (!date) return | |||||
| setValue("productionDate", date.format(INPUT_DATE_FORMAT)); | |||||
| // field.onChange(date); | |||||
| }} | |||||
| inputRef={field.ref} | |||||
| slotProps={{ | |||||
| textField: { | |||||
| // required: true, | |||||
| error: Boolean(errors.productionDate?.message), | |||||
| helperText: errors.productionDate?.message, | |||||
| }, | |||||
| }} | |||||
| /> | |||||
| </LocalizationProvider> | |||||
| ); | |||||
| }} | |||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| <Grid item xs={4}> | <Grid item xs={4}> | ||||
| <TextField | |||||
| label={t("expiryDate")} | |||||
| fullWidth | |||||
| {...register("expiryDate", { | |||||
| required: "expiryDate required!", | |||||
| })} | |||||
| error={Boolean(errors.expiryDate)} | |||||
| helperText={errors.expiryDate?.message} | |||||
| <Controller | |||||
| control={control} | |||||
| name="expiryDate" | |||||
| // rules={{ required: !Boolean(productionDate) }} | |||||
| render={({ field }) => { | |||||
| return ( | |||||
| <LocalizationProvider | |||||
| dateAdapter={AdapterDayjs} | |||||
| adapterLocale={`${language}-hk`} | |||||
| > | |||||
| <DatePicker | |||||
| {...field} | |||||
| sx={{ width: "100%" }} | |||||
| label={t("expiryDate")} | |||||
| value={expiryDate ? dayjs(expiryDate) : undefined} | |||||
| onChange={(date) => { | |||||
| console.log(date) | |||||
| if (!date) return | |||||
| console.log(date.format(INPUT_DATE_FORMAT)) | |||||
| setValue("expiryDate", date.format(INPUT_DATE_FORMAT)); | |||||
| // field.onChange(date); | |||||
| }} | |||||
| inputRef={field.ref} | |||||
| slotProps={{ | |||||
| textField: { | |||||
| // required: true, | |||||
| error: Boolean(errors.expiryDate?.message), | |||||
| helperText: errors.expiryDate?.message, | |||||
| }, | |||||
| }} | |||||
| /> | |||||
| </LocalizationProvider> | |||||
| ); | |||||
| }} | |||||
| /> | /> | ||||
| </Grid> | </Grid> | ||||
| </Grid> | </Grid> | ||||
| @@ -52,6 +52,14 @@ const PoSearch: React.FC<Props> = ({ po }) => { | |||||
| name: "code", | name: "code", | ||||
| label: t("Code"), | label: t("Code"), | ||||
| }, | }, | ||||
| { | |||||
| name: "orderDate", | |||||
| label: t("OrderDate"), | |||||
| }, | |||||
| { | |||||
| name: "status", | |||||
| label: t("Status"), | |||||
| }, | |||||
| // { | // { | ||||
| // name: "name", | // name: "name", | ||||
| // label: t("Name"), | // label: t("Name"), | ||||
| @@ -7,6 +7,10 @@ import { notFound } from "next/navigation"; | |||||
| import PoSearchLoading from "./PoSearchLoading"; | import PoSearchLoading from "./PoSearchLoading"; | ||||
| import PoSearch from "./PoSearch"; | import PoSearch from "./PoSearch"; | ||||
| import { fetchPoList, PoResult } from "@/app/api/po"; | import { fetchPoList, PoResult } from "@/app/api/po"; | ||||
| import dayjs from "dayjs"; | |||||
| import arraySupport from "dayjs/plugin/arraySupport"; | |||||
| import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; | |||||
| dayjs.extend(arraySupport); | |||||
| interface SubComponents { | interface SubComponents { | ||||
| Loading: typeof PoSearchLoading; | Loading: typeof PoSearchLoading; | ||||
| @@ -26,8 +30,14 @@ const PoSearchWrapper: React.FC<Props> & SubComponents = async ( | |||||
| ] = await Promise.all([ | ] = await Promise.all([ | ||||
| fetchPoList() | fetchPoList() | ||||
| ]); | ]); | ||||
| return <PoSearch po={po} />; | |||||
| console.log(po) | |||||
| const fixPoDate = po.map((p) => { | |||||
| return ({ | |||||
| ...p, | |||||
| orderDate: dayjs(p.orderDate).format(OUTPUT_DATE_FORMAT) | |||||
| }) | |||||
| }) | |||||
| return <PoSearch po={fixPoDate} />; | |||||
| }; | }; | ||||
| PoSearchWrapper.Loading = PoSearchLoading; | PoSearchWrapper.Loading = PoSearchLoading; | ||||
| @@ -29,7 +29,7 @@ const style = { | |||||
| export var defaultScannerConfig: ScannerConfig = { | export var defaultScannerConfig: ScannerConfig = { | ||||
| onUpdate: (err, result) => { | onUpdate: (err, result) => { | ||||
| if (result) { | if (result) { | ||||
| const data = JSON.parse(result.getText()) | |||||
| const data = JSON.parse(result.getText()); | |||||
| console.log(data); | console.log(data); | ||||
| } else return; | } else return; | ||||
| }, | }, | ||||
| @@ -48,19 +48,27 @@ export interface ScannerConfig { | |||||
| delay?: number; // Delay between scans in milliseconds. Default is 500ms. | delay?: number; // Delay between scans in milliseconds. Default is 500ms. | ||||
| videoConstraints?: MediaTrackConstraints; // Video constraints to pass to the webcam. If not provided, the default constraints will be used. | videoConstraints?: MediaTrackConstraints; // Video constraints to pass to the webcam. If not provided, the default constraints will be used. | ||||
| formats?: BarcodeFormat[] | BarcodeStringFormat[]; // Array of barcode formats to decode. If not provided, all formats will be used. A smaller list may improve the speed of the scan. | formats?: BarcodeFormat[] | BarcodeStringFormat[]; // Array of barcode formats to decode. If not provided, all formats will be used. A smaller list may improve the speed of the scan. | ||||
| stopStream?: boolean | |||||
| stopStream?: boolean; | |||||
| } | } | ||||
| const ReactQrCodeScanner: React.FC<Props> = ({ | |||||
| scannerConfig, | |||||
| }) => { | |||||
| const [stopStream, setStopStream] = useState(scannerConfig.stopStream || defaultScannerConfig.stopStream || false); | |||||
| const ReactQrCodeScanner: React.FC<Props> = ({ scannerConfig }) => { | |||||
| const [stopStream, setStopStream] = useState( | |||||
| scannerConfig.stopStream || defaultScannerConfig.stopStream || false | |||||
| ); | |||||
| const [torchEnabled, setTorchEnabled] = useState<boolean>(false); | const [torchEnabled, setTorchEnabled] = useState<boolean>(false); | ||||
| const _scannerConfig = useMemo(() => ({ | |||||
| ...defaultScannerConfig, | |||||
| ...scannerConfig, | |||||
| }),[]) | |||||
| // const _scannerConfig = useMemo(() => ({ | |||||
| // ...defaultScannerConfig, | |||||
| // ...scannerConfig, | |||||
| // }),[]) | |||||
| const [_scannerConfig, setScannerConfig] = useState<ScannerConfig>({ | |||||
| ...defaultScannerConfig | |||||
| }); | |||||
| useEffect(() => { | |||||
| setScannerConfig({ | |||||
| ...defaultScannerConfig, | |||||
| ...scannerConfig, | |||||
| }); | |||||
| }, []); | |||||
| const SwitchOnOffScanner = useCallback(() => { | const SwitchOnOffScanner = useCallback(() => { | ||||
| // Stop the QR Reader stream (fixes issue where the browser freezes when closing the modal) and then dismiss the modal one tick later | // Stop the QR Reader stream (fixes issue where the browser freezes when closing the modal) and then dismiss the modal one tick later | ||||
| setStopStream((prev) => !prev); | setStopStream((prev) => !prev); | ||||
| @@ -71,21 +79,19 @@ const ReactQrCodeScanner: React.FC<Props> = ({ | |||||
| }, []); | }, []); | ||||
| return ( | return ( | ||||
| <> | |||||
| {!stopStream ? ( | |||||
| <BarcodeScanner | |||||
| stopStream={stopStream} | |||||
| torch={torchEnabled} | |||||
| {..._scannerConfig} | |||||
| /> | |||||
| ) : undefined} | |||||
| <Button onClick={SwitchOnOffTorch}> | |||||
| {torchEnabled ? "off" : "on"} | |||||
| </Button> | |||||
| <Button onClick={SwitchOnOffScanner}> | |||||
| {stopStream ? "start" : "stop"} | |||||
| </Button> | |||||
| </> | |||||
| <> | |||||
| {!stopStream ? ( | |||||
| <BarcodeScanner | |||||
| stopStream={stopStream} | |||||
| torch={torchEnabled} | |||||
| {..._scannerConfig} | |||||
| /> | |||||
| ) : undefined} | |||||
| <Button onClick={SwitchOnOffTorch}>{torchEnabled ? "off" : "on"}</Button> | |||||
| <Button onClick={SwitchOnOffScanner}> | |||||
| {stopStream ? "start" : "stop"} | |||||
| </Button> | |||||
| </> | |||||
| ); | ); | ||||
| }; | }; | ||||
| export default ReactQrCodeScanner; | export default ReactQrCodeScanner; | ||||