diff --git a/src/app/(main)/po/page.tsx b/src/app/(main)/po/page.tsx index 21f4eb9..6770399 100644 --- a/src/app/(main)/po/page.tsx +++ b/src/app/(main)/po/page.tsx @@ -14,8 +14,8 @@ export const metadata: Metadata = { title: "Purchase Order", }; -const production: React.FC = async () => { - const { t } = await getServerI18n("claims"); +const PurchaseOrder: React.FC = async () => { + const { t } = await getServerI18n("purchaseOrder"); // preloadClaims(); return ( @@ -45,4 +45,4 @@ const production: React.FC = async () => { ); }; -export default production; +export default PurchaseOrder; diff --git a/src/app/api/po/actions.ts b/src/app/api/po/actions.ts index 5c3daa7..762086e 100644 --- a/src/app/api/po/actions.ts +++ b/src/app/api/po/actions.ts @@ -15,7 +15,8 @@ export interface PostStockInLiineResponse { type?: string message: string | null; errorPosition: string | keyof T; - entity: StockInLine | StockInLine[] + entity: T | T[] + // entity: StockInLine | StockInLine[] } export interface StockInLineEntry { @@ -83,6 +84,7 @@ export const createStockInLine = async (data: StockInLineEntry) => { body: JSON.stringify(data), headers: { "Content-Type": "application/json" }, }); + // revalidateTag("po"); return stockInLine } @@ -92,16 +94,34 @@ export const updateStockInLine = async (data: StockInLineEntry & ModalFormInput) body: JSON.stringify(data), headers: { "Content-Type": "application/json" }, }); + // revalidateTag("po"); return stockInLine } +export const startPo = async (poId: number) => { + const po = await serverFetchJson>(`${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) => { - const po = await serverFetchJson>(`${BASE_API_URL}/po//check/${poId}`, { + const po = await serverFetchJson>(`${BASE_API_URL}/po/check/${poId}`, { method: "POST", body: JSON.stringify({ poId }), headers: { "Content-Type": "application/json" }, }); + revalidateTag("po"); return po } +export const fetchPoInClient = cache(async (id: number) => { + return serverFetchJson(`${BASE_API_URL}/po/detail/${id}`, { + next: { tags: ["po"] }, + }); +}); + diff --git a/src/app/api/po/index.ts b/src/app/api/po/index.ts index 375f98c..5dc3d3d 100644 --- a/src/app/api/po/index.ts +++ b/src/app/api/po/index.ts @@ -41,8 +41,10 @@ export interface StockInLine { acceptedQty: number price: number priceUnit: string - productionDate: string - expiryDate: string + shelfLife?: number, + receiptDate?: string + productionDate?: string + expiryDate?: string status: string supplier: string lotNo: string diff --git a/src/app/utils/formatUtil.ts b/src/app/utils/formatUtil.ts index 802a8a0..044d6b6 100644 --- a/src/app/utils/formatUtil.ts +++ b/src/app/utils/formatUtil.ts @@ -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 } = { "draft": 0, "pending": 1, diff --git a/src/components/PoDetail/PoDetail.tsx b/src/components/PoDetail/PoDetail.tsx index 56baf92..676c4ab 100644 --- a/src/components/PoDetail/PoDetail.tsx +++ b/src/components/PoDetail/PoDetail.tsx @@ -36,8 +36,10 @@ import { } from "@mui/x-data-grid"; import { checkPolAndCompletePo, + fetchPoInClient, fetchStockInLineInfo, PurchaseQcResult, + startPo, testFetch, } from "@/app/api/po/actions"; import { @@ -88,14 +90,46 @@ const PoDetail: React.FC = ({ po, qc, warehouse }) => { console.log(cameras); const { t } = useTranslation(); const apiRef = useGridApiRef(); - const [rows, setRows] = useState(po.pol || []); + const [purchaseOrder, setPurchaseOrder] = useState({ ...po }); + const [rows, setRows] = useState( + purchaseOrder.pol || [] + ); 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 }) { const { row } = props; const [open, setOpen] = useState(false); const [processedQty, setProcessedQty] = useState(row.processed); 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(() => { if (processedQty === row.qty) { setCurrStatus("completed".toUpperCase()); @@ -106,20 +140,12 @@ const PoDetail: React.FC = ({ po, qc, warehouse }) => { } }, [processedQty]); - const totalWeight = useMemo( - () => calculateWeight(row.qty, row.uom), - [calculateWeight] - ); - const weightUnit = useMemo( - () => returnWeightUnit(row.uom), - [returnWeightUnit] - ); - return ( <> *": { borderBottom: "unset" }, color: "black" }}> setOpen(!open)} @@ -152,9 +178,10 @@ const PoDetail: React.FC = ({ po, qc, warehouse }) => { @@ -199,20 +226,6 @@ const PoDetail: React.FC = ({ po, qc, warehouse }) => { 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 ( <> = ({ po, qc, warehouse }) => { // component="form" // onSubmit={formProps.handleSubmit(onSubmit, onSubmitError)} > - + - {po.code} + {/* {purchaseOrder.code} - {currPoStatus} */} + {purchaseOrder.code} - {purchaseOrder.status} + + {purchaseOrder.status.toLowerCase() === "pending" && ( + + + + )} + {purchaseOrder.status.toLowerCase() === "receiving" && ( + + + + )} + {/* */} @@ -242,9 +268,15 @@ const PoDetail: React.FC = ({ po, qc, warehouse }) => { {/* */} - {/* scanner */} + {/* scanner */} {/* */} - + >; + setStockInLine: Dispatch>; setProcessedQty: Dispatch>; itemDetail: PurchaseOrderLine; stockInLine: StockInLine[]; @@ -100,6 +101,7 @@ class ProcessRowUpdateError extends Error { function PoInputGrid({ qc, setRows, + setStockInLine, setProcessedQty, itemDetail, stockInLine, @@ -122,6 +124,7 @@ function PoInputGrid({ const [escalOpen, setEscalOpen] = useState(false); const [stockInOpen, setStockInOpen] = useState(false); const [putAwayOpen, setPutAwayOpen] = useState(false); + const [btnIsLoading, setBtnIsLoading] = useState(false); const [currQty, setCurrQty] = useState(() => { const total = entries.reduce( (acc, curr) => acc + (curr.acceptedQty || 0), @@ -147,17 +150,14 @@ function PoInputGrid({ ); const handleStart = useCallback( (id: GridRowId, params: any) => () => { + setBtnIsLoading(true); setRowModesModel((prev) => ({ ...prev, [id]: { mode: GridRowModes.View }, })); setTimeout(async () => { // post stock in line - console.log("delayed"); - console.log(params); const oldId = params.row.id; - console.log(oldId); - console.log(params.row); const postData = { itemId: params.row.itemId, itemNo: params.row.itemNo, @@ -171,6 +171,13 @@ function PoInputGrid({ setEntries((prev) => prev.map((p) => (p.id === oldId ? (res.entity as StockInLine) : p)) ); + setStockInLine( + (prev) => + prev.map((p) => + p.id === oldId ? (res.entity as StockInLine) : p + ) as StockInLine[] + ); + setBtnIsLoading(false); // do post directly to test // openStartModal(); }, 200); @@ -183,6 +190,7 @@ function PoInputGrid({ const handleQC = useCallback( (id: GridRowId, params: any) => async () => { + setBtnIsLoading(true); setRowModesModel((prev) => ({ ...prev, [id]: { mode: GridRowModes.View }, @@ -199,12 +207,14 @@ function PoInputGrid({ // open qc modal console.log("delayed"); openQcModal(); + setBtnIsLoading(false); }, 200); }, [fetchQcDefaultValue] ); const handleEscalation = useCallback( (id: GridRowId, params: any) => () => { + // setBtnIsLoading(true); setRowModesModel((prev) => ({ ...prev, [id]: { mode: GridRowModes.View }, @@ -214,12 +224,14 @@ function PoInputGrid({ // open qc modal console.log("delayed"); openEscalationModal(); + // setBtnIsLoading(false); }, 200); }, [] ); const handleStockIn = useCallback( (id: GridRowId, params: any) => () => { + // setBtnIsLoading(true); setRowModesModel((prev) => ({ ...prev, [id]: { mode: GridRowModes.View }, @@ -231,6 +243,7 @@ function PoInputGrid({ // return the record with its status as pending // update layout console.log("delayed"); + // setBtnIsLoading(false); }, 200); }, [] @@ -238,6 +251,7 @@ function PoInputGrid({ const handlePutAway = useCallback( (id: GridRowId, params: any) => () => { + // setBtnIsLoading(true); setRowModesModel((prev) => ({ ...prev, [id]: { mode: GridRowModes.View }, @@ -249,6 +263,7 @@ function PoInputGrid({ // return the record with its status as pending // update layout console.log("delayed"); + // setBtnIsLoading(false); }, 200); }, [] @@ -256,6 +271,7 @@ function PoInputGrid({ const printQrcode = useCallback( async (row: any) => { + setBtnIsLoading(true); console.log(row.id); const postData = { stockInLineIds: [row.id] }; // const postData = { stockInLineIds: [42,43,44] }; @@ -264,6 +280,7 @@ function PoInputGrid({ console.log(response); downloadFile(new Uint8Array(response.blobValue), response.filename!!); } + setBtnIsLoading(false); }, [fetchPoQrcode, downloadFile] ); @@ -319,11 +336,11 @@ function PoInputGrid({ () => [ { field: "itemNo", - flex: 0.8, + flex: 0.4, }, { field: "itemName", - flex: 1, + flex: 0.6, }, { field: "acceptedQty", @@ -363,7 +380,7 @@ function PoInputGrid({ field: "actions", type: "actions", headerName: "start | qc | escalation | stock in | putaway | delete", - flex: 1, + flex: 1.5, cellClassName: "actions", getActions: (params) => { console.log(params.row.status); @@ -376,7 +393,7 @@ function PoInputGrid({ color: "primary.main", // marginRight: 1, }} - disabled={!(stockInLineStatusMap[status] === 0)} + disabled={btnIsLoading || !(stockInLineStatusMap[status] === 0)} // set _isNew to false after posting // or check status onClick={handleStart(params.row.id, params)} @@ -390,7 +407,7 @@ function PoInputGrid({ color: "primary.main", // marginRight: 1, }} - disabled={stockInLineStatusMap[status] < 1} + disabled={btnIsLoading || stockInLineStatusMap[status] < 1} // set _isNew to false after posting // or check status onClick={handleQC(params.row.id, params)} @@ -405,8 +422,9 @@ function PoInputGrid({ // marginRight: 1, }} disabled={ + btnIsLoading || stockInLineStatusMap[status] <= 0 || - stockInLineStatusMap[status] >= 4 + stockInLineStatusMap[status] >= 5 } // set _isNew to false after posting // or check status @@ -421,7 +439,11 @@ function PoInputGrid({ color: "primary.main", // marginRight: 1, }} - disabled={stockInLineStatusMap[status] <= 2 || stockInLineStatusMap[status] >= 7} + disabled={ + btnIsLoading || + stockInLineStatusMap[status] <= 2 || + stockInLineStatusMap[status] >= 7 + } // set _isNew to false after posting // or check status onClick={handleStockIn(params.row.id, params)} @@ -435,7 +457,7 @@ function PoInputGrid({ color: "primary.main", // marginRight: 1, }} - disabled={stockInLineStatusMap[status] < 7} + disabled={btnIsLoading || stockInLineStatusMap[status] < 7} // set _isNew to false after posting // or check status onClick={handlePutAway(params.row.id, params)} @@ -449,7 +471,7 @@ function PoInputGrid({ color: "primary.main", // marginRight: 1, }} - disabled={stockInLineStatusMap[status] !== 8} + disabled={btnIsLoading || stockInLineStatusMap[status] !== 8} // set _isNew to false after posting // or check status onClick={handleQrCode(params.row.id, params)} @@ -462,7 +484,7 @@ function PoInputGrid({ sx={{ color: "error.main", }} - disabled={stockInLineStatusMap[status] !== 0} + disabled={btnIsLoading || stockInLineStatusMap[status] !== 0} // disabled={Boolean(params.row.status)} onClick={handleDelete(params.row.id)} color="inherit" @@ -472,7 +494,7 @@ function PoInputGrid({ }, }, ], - [] + [btnIsLoading] ); const addRow = useCallback(() => { @@ -533,6 +555,8 @@ function PoInputGrid({ const newEntries = entries.map((e) => getRowId(e) === getRowId(originalRow) ? rowToSave : e ); + setStockInLine(newEntries as StockInLine[]); + console.log("triggered"); setEntries(newEntries); //update remaining qty const total = newEntries.reduce( @@ -569,9 +593,7 @@ function PoInputGrid({ ); - useEffect(() => { - console.log(modalInfo); - }, [modalInfo]); + return ( <> { + // setRows: Dispatch>; setEntries?: Dispatch>; + setStockInLine?: Dispatch>; itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] }; - setItemDetail: Dispatch> + setItemDetail: Dispatch< + SetStateAction< + | (StockInLine & { + warehouseId?: number; + }) + | undefined + > + >; qc?: QcItemWithChecks[]; warehouse?: any[]; type: "qc" | "stockIn" | "escalation" | "putaway"; @@ -75,7 +87,9 @@ const style = { const PoQcStockInModal: React.FC = ({ type, + // setRows, setEntries, + setStockInLine, open, onClose, itemDetail, @@ -86,12 +100,16 @@ const PoQcStockInModal: React.FC = ({ const [serverError, setServerError] = useState(""); const { t } = useTranslation(); const params = useSearchParams(); + const [btnIsLoading, setBtnIsLoading] = useState(false); + console.log(params.get("id")); console.log(itemDetail); console.log(itemDetail.qcResult); const formProps = useForm({ defaultValues: { ...itemDetail, + // receiptDate: itemDetail.receiptDate || dayjs().add(-1, "month").format(INPUT_DATE_FORMAT), + // warehouseId: itemDetail.defaultWarehouseId || 0 }, }); // console.log(formProps); @@ -111,40 +129,95 @@ const PoQcStockInModal: React.FC = ({ } }, [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>( async (data, event) => { + formProps.clearErrors(); let hasErrors = false; - + setBtnIsLoading(true); console.log(errors); console.log(data); console.log(itemDetail); - console.log(data.receiptDate) - console.log(fix0IndexedDate(data.receiptDate)) + // console.log(fix0IndexedDate(data.receiptDate)); try { // 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 ////////////////////// var productionDate = null; var expiryDate = null; var receiptDate = 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 (data.qcResult) { @@ -163,17 +236,20 @@ const PoQcStockInModal: React.FC = ({ receiptDate: receiptDate, } as StockInLineEntry & ModalFormInput; ////////////////////////////////////////////////////////////////////// - console.log(args); - // return if (hasErrors) { + console.log(args); 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); if (Boolean(res.id)) { // update entries const newEntries = res.entity as StockInLine[]; - console.log(newEntries) + console.log(newEntries); if (setEntries) { setEntries((prev) => { const updatedEntries = [...prev]; // Create a new array @@ -181,7 +257,24 @@ const PoQcStockInModal: React.FC = ({ const index = updatedEntries.findIndex((p) => p.id === item.id); if (index !== -1) { // 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; } else { // Add new item @@ -192,31 +285,56 @@ const PoQcStockInModal: React.FC = ({ }); } // add loading - setItemDetail(undefined) + setBtnIsLoading(false); + + setItemDetail(undefined); closeHandler({}, "backdropClick"); } console.log(res); // if (res) } catch (e) { // server error + setBtnIsLoading(false); setServerError(t("An error has occurred. Please try again later.")); 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 => { if (itemDetail) { const status = itemDetail.status; console.log(status); switch (type) { case "qc": - return stockInLineStatusMap[status] >= 1 && stockInLineStatusMap[status] <= 2; + return ( + stockInLineStatusMap[status] >= 1 && + stockInLineStatusMap[status] <= 2 + ); case "escalation": - return stockInLineStatusMap[status] === 1 || stockInLineStatusMap[status] >= 3 || stockInLineStatusMap[status] <= 5; + return ( + stockInLineStatusMap[status] === 1 || + stockInLineStatusMap[status] >= 3 || + stockInLineStatusMap[status] <= 5 + ); case "stockIn": - return stockInLineStatusMap[status] >= 3 && stockInLineStatusMap[status] <= 6; + return ( + stockInLineStatusMap[status] >= 3 && + stockInLineStatusMap[status] <= 6 + ); case "putaway": return stockInLineStatusMap[status] === 7; default: @@ -248,19 +366,30 @@ const PoQcStockInModal: React.FC = ({ {itemDetail !== undefined && type === "putaway" && ( )} - {renderSubmitButton ? ( - + + {renderSubmitButton ? ( - - ) : undefined} + ) : undefined} + {itemDetail !== undefined && type === "putaway" && ( + + )} + diff --git a/src/components/PoDetail/PutawayForm.tsx b/src/components/PoDetail/PutawayForm.tsx index ee7418f..4c89fa8 100644 --- a/src/components/PoDetail/PutawayForm.tsx +++ b/src/components/PoDetail/PutawayForm.tsx @@ -16,7 +16,7 @@ import { Tooltip, Typography, } from "@mui/material"; -import { useFormContext } from "react-hook-form"; +import { Controller, useFormContext } from "react-hook-form"; import { useTranslation } from "react-i18next"; import StyledDataGrid from "../StyledDataGrid"; import { useCallback, useEffect, useMemo, useState } from "react"; @@ -40,7 +40,9 @@ import { WarehouseResult } from "@/app/api/warehouse"; import { stockInLineStatusMap } from "@/app/utils/formatUtil"; import { QRCodeSVG } from "qrcode.react"; import { QrCode } from "../QrCode"; -import ReactQrCodeScanner, { ScannerConfig } from "../ReactQrCodeScanner/ReactQrCodeScanner"; +import ReactQrCodeScanner, { + ScannerConfig, +} from "../ReactQrCodeScanner/ReactQrCodeScanner"; import { QrCodeInfo } from "@/app/api/qrcode"; interface Props { @@ -84,19 +86,24 @@ const PutawayForm: React.FC = ({ itemDetail, warehouse }) => { clearErrors, } = useFormContext(); 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(() => { // do filtering here if any return warehouse; }, []); + const defaultOption = { + value: 0, // think think sin + label: t("Select warehouse"), + group: "default", + } const options = useMemo(() => { 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) => ({ value: w.id, label: `${w.code} - ${w.name}`, @@ -107,8 +114,8 @@ const PutawayForm: React.FC = ({ itemDetail, warehouse }) => { const currentValue = warehouseId > 0 ? 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( ( event: React.SyntheticEvent, @@ -119,19 +126,47 @@ const PutawayForm: React.FC = ({ itemDetail, warehouse }) => { group: string; }; console.log(singleNewVal); - setValue("warehouseId", singleNewVal.value); + console.log("onChange"); + // setValue("warehouseId", 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( () => ({ stockInLineId: itemDetail.id, itemId: itemDetail.itemId, lotNo: itemDetail.lotNo, - // warehouseId: 1 // for testing + // warehouseId: 2 // for testing // expiryDate: itemDetail.expiryDate, // productionDate: itemDetail.productionDate, // supplier: itemDetail.supplier, @@ -140,7 +175,7 @@ const PutawayForm: React.FC = ({ itemDetail, warehouse }) => { [itemDetail] ); const [isOpenScanner, setOpenScanner] = useState(false); - + const closeHandler = useCallback>( (...args) => { setOpenScanner(false); @@ -158,23 +193,33 @@ const PutawayForm: React.FC = ({ itemDetail, warehouse }) => { const scannerConfig = useMemo( () => ({ onUpdate: (err, result) => { + console.log(result); + console.log(Boolean(result)); if (result) { const data: QrCodeInfo = JSON.parse(result.getText()); console.log(data); if (data.warehouseId) { + console.log(data.warehouseId); setWarehouseId(data.warehouseId); - onCloseScanner() + onCloseScanner(); } } else return; }, }), - [] + [onCloseScanner] ); useEffect(() => { setValue("status", "completed"); }, []); + useEffect(() => { + if (warehouseId > 0) { + setValue("warehouseId", warehouseId); + clearErrors("warehouseId") + } + }, [warehouseId]); + return ( @@ -260,11 +305,13 @@ const PutawayForm: React.FC = ({ itemDetail, warehouse }) => { disableClearable disabled fullWidth - defaultValue={options.find((o) => o.value === 1)} + defaultValue={options.find((o) => o.value === 1)} /// modify this later // onChange={onChange} getOptionLabel={(option) => option.label} options={options} - renderInput={(params) => } + renderInput={(params) => ( + + )} /> @@ -278,25 +325,63 @@ const PutawayForm: React.FC = ({ itemDetail, warehouse }) => { max: itemDetail.acceptedQty, valueAsNumber: true, })} - defaultValue={itemDetail.acceptedQty} + // defaultValue={itemDetail.acceptedQty} error={Boolean(errors.acceptedQty)} helperText={errors.acceptedQty?.message} /> - + + {/* { + console.log(field); + return ( + o.value == field.value)} + onChange={onChange} + getOptionLabel={(option) => option.label} + options={options} + renderInput={(params) => ( + + )} + /> + ); + }} + /> */} 0 + // ? options.find((o) => o.value === warehouseId) + // : undefined} value={currentValue} onChange={onChange} getOptionLabel={(option) => option.label} options={options} - renderInput={(params) => } + renderInput={(params) => ( + + )} /> @@ -318,11 +403,11 @@ const PutawayForm: React.FC = ({ itemDetail, warehouse }) => { */} - - - - - + + + + + ); }; diff --git a/src/components/PoDetail/QcForm.tsx b/src/components/PoDetail/QcForm.tsx index b9d4337..2dabc30 100644 --- a/src/components/PoDetail/QcForm.tsx +++ b/src/components/PoDetail/QcForm.tsx @@ -38,7 +38,7 @@ import { NEXT_PUBLIC_API_URL } from "@/config/api"; import axiosInstance from "@/app/(main)/axios/axiosInstance"; interface Props { - itemDetail: StockInLine + itemDetail: StockInLine; qc: QcItemWithChecks[]; } type EntryError = @@ -65,9 +65,37 @@ const QcForm: React.FC = ({ qc, itemDetail }) => { clearErrors, } = useFormContext(); 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( () => [ { @@ -105,7 +133,7 @@ const QcForm: React.FC = ({ qc, itemDetail }) => { // id: params.id, // field: "type", // value: "determine1", - // }); + // }); }} /> ); @@ -144,6 +172,7 @@ const QcForm: React.FC = ({ qc, itemDetail }) => { ], [qc] ); + /// validate datagrid const validation = useCallback( (newRow: GridRowModel): EntryError => { const error: EntryError = {}; @@ -161,17 +190,17 @@ const QcForm: React.FC = ({ qc, itemDetail }) => { }, [] ); - + useEffect(() => { - console.log(itemDetail) - var status = "receiving" + console.log(itemDetail); + var status = "receiving"; // switch (itemDetail.status) { // case 'pending': // status = "receiving" // break; // } - setValue("status", status) - }, [itemDetail]) + setValue("status", status); + }, [itemDetail]); return ( @@ -187,21 +216,22 @@ const QcForm: React.FC = ({ qc, itemDetail }) => { spacing={2} sx={{ mt: 0.5 }} > - + - + {/* = ({ qc, itemDetail }) => { // error={Boolean(errors.sampleRate)} // helperText={errors.sampleRate?.message} /> - + */} = ({ qc, itemDetail }) => { = ({ qc, itemDetail }) => { = ({ qc, itemDetail }) => { _formKey={"qcResult"} columns={columns} validateRow={validation} - needAdd={itemDetail.status === "qc" || itemDetail.status === "pending"} + needAdd={ + itemDetail.status === "qc" || itemDetail.status === "pending" + } /> diff --git a/src/components/PoDetail/StockInForm.tsx b/src/components/PoDetail/StockInForm.tsx index 3557e07..64a9624 100644 --- a/src/components/PoDetail/StockInForm.tsx +++ b/src/components/PoDetail/StockInForm.tsx @@ -1,6 +1,10 @@ "use client"; -import { PurchaseQcResult, PurchaseQCInput, StockInInput } from "@/app/api/po/actions"; +import { + PurchaseQcResult, + PurchaseQCInput, + StockInInput, +} from "@/app/api/po/actions"; import { Box, Card, @@ -11,7 +15,7 @@ import { Tooltip, Typography, } from "@mui/material"; -import { useFormContext } from "react-hook-form"; +import { Controller, useFormContext } from "react-hook-form"; import { useTranslation } from "react-i18next"; import StyledDataGrid from "../StyledDataGrid"; import { useCallback, useEffect, useMemo } from "react"; @@ -31,6 +35,10 @@ import QcSelect from "./QcSelect"; import { QcItemWithChecks } from "@/app/api/qc"; import { GridEditInputCell } from "@mui/x-data-grid"; 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 interface Props { itemDetail: StockInLine; @@ -44,11 +52,14 @@ type EntryError = // type PoQcRow = TableRow, EntryError>; -const StockInForm: React.FC = ({ +const StockInForm: React.FC = ({ // qc, itemDetail, - }) => { - const { t } = useTranslation(); +}) => { + const { + t, + i18n: { language }, + } = useTranslation(); const apiRef = useGridApiRef(); const { register, @@ -62,18 +73,33 @@ const StockInForm: React.FC = ({ setError, clearErrors, } = useFormContext(); - 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(() => { - 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 ( - {t("Qc Detail")} + {t("Stock In Detail")} = ({ /> - { + return ( + + { + 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, + }, + }} + /> + + ); + }} /> @@ -128,25 +178,75 @@ const StockInForm: React.FC = ({ /> - { + return ( + + { + 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, + }, + }} + /> + + ); + }} /> - { + return ( + + { + 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, + }, + }} + /> + + ); + }} /> diff --git a/src/components/PoSearch/PoSearch.tsx b/src/components/PoSearch/PoSearch.tsx index 87cc49e..2bf1e37 100644 --- a/src/components/PoSearch/PoSearch.tsx +++ b/src/components/PoSearch/PoSearch.tsx @@ -52,6 +52,14 @@ const PoSearch: React.FC = ({ po }) => { name: "code", label: t("Code"), }, + { + name: "orderDate", + label: t("OrderDate"), + }, + { + name: "status", + label: t("Status"), + }, // { // name: "name", // label: t("Name"), diff --git a/src/components/PoSearch/PoSearchWrapper.tsx b/src/components/PoSearch/PoSearchWrapper.tsx index 8d3f5b4..f937539 100644 --- a/src/components/PoSearch/PoSearchWrapper.tsx +++ b/src/components/PoSearch/PoSearchWrapper.tsx @@ -7,6 +7,10 @@ import { notFound } from "next/navigation"; import PoSearchLoading from "./PoSearchLoading"; import PoSearch from "./PoSearch"; 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 { Loading: typeof PoSearchLoading; @@ -26,8 +30,14 @@ const PoSearchWrapper: React.FC & SubComponents = async ( ] = await Promise.all([ fetchPoList() ]); - - return ; + console.log(po) + const fixPoDate = po.map((p) => { + return ({ + ...p, + orderDate: dayjs(p.orderDate).format(OUTPUT_DATE_FORMAT) + }) + }) + return ; }; PoSearchWrapper.Loading = PoSearchLoading; diff --git a/src/components/ReactQrCodeScanner/ReactQrCodeScanner.tsx b/src/components/ReactQrCodeScanner/ReactQrCodeScanner.tsx index a7bf108..3642b3d 100644 --- a/src/components/ReactQrCodeScanner/ReactQrCodeScanner.tsx +++ b/src/components/ReactQrCodeScanner/ReactQrCodeScanner.tsx @@ -29,7 +29,7 @@ const style = { export var defaultScannerConfig: ScannerConfig = { onUpdate: (err, result) => { if (result) { - const data = JSON.parse(result.getText()) + const data = JSON.parse(result.getText()); console.log(data); } else return; }, @@ -48,19 +48,27 @@ export interface ScannerConfig { 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. 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 = ({ - scannerConfig, -}) => { - const [stopStream, setStopStream] = useState(scannerConfig.stopStream || defaultScannerConfig.stopStream || false); +const ReactQrCodeScanner: React.FC = ({ scannerConfig }) => { + const [stopStream, setStopStream] = useState( + scannerConfig.stopStream || defaultScannerConfig.stopStream || false + ); const [torchEnabled, setTorchEnabled] = useState(false); - const _scannerConfig = useMemo(() => ({ - ...defaultScannerConfig, - ...scannerConfig, - }),[]) - + // const _scannerConfig = useMemo(() => ({ + // ...defaultScannerConfig, + // ...scannerConfig, + // }),[]) + const [_scannerConfig, setScannerConfig] = useState({ + ...defaultScannerConfig + }); + useEffect(() => { + setScannerConfig({ + ...defaultScannerConfig, + ...scannerConfig, + }); + }, []); 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 setStopStream((prev) => !prev); @@ -71,21 +79,19 @@ const ReactQrCodeScanner: React.FC = ({ }, []); return ( - <> - {!stopStream ? ( - - ) : undefined} - - - + <> + {!stopStream ? ( + + ) : undefined} + + + ); }; export default ReactQrCodeScanner;