"use client"; import { Box, Button, Grid, Modal, ModalProps, Stack, TextField, Typography, Paper, Divider, } from "@mui/material"; import { useCallback, useEffect, useLayoutEffect, useMemo, useState } from "react"; import ReactQrCodeScanner, { ScannerConfig, } from "../ReactQrCodeScanner/ReactQrCodeScanner"; import { FormProvider, SubmitHandler, useForm } from "react-hook-form"; import { fetchStockInLineInfo, StockInLineEntry, updateStockInLine, } from "@/app/api/stockIn/actions"; import { ModalFormInput, StockInLine } from "@/app/api/stockIn"; import { WarehouseResult } from "@/app/api/warehouse"; // import { QrCodeInfo } from "@/app/api/qrcde"; import { Check, QrCode, ErrorOutline, CheckCircle } from "@mui/icons-material"; import { useTranslation } from "react-i18next"; import { useSearchParams } from "next/navigation"; import { useQrCodeScannerContext } from "../QrCodeScannerProvider/QrCodeScannerProvider"; import LoadingComponent from "../General/LoadingComponent"; import StockInForm from "../StockIn/StockInForm"; import { arrayToDateString, INPUT_DATE_FORMAT } from "@/app/utils/formatUtil"; import { QrCodeScanner } from "../QrCodeScannerProvider/QrCodeScannerProvider"; import { msg } from "../Swal/CustomAlerts"; import { PutAwayRecord } from "."; import FgStockInForm from "../StockIn/FgStockInForm"; import Swal from "sweetalert2"; interface Props extends Omit { warehouse: WarehouseResult[]; stockInLineId: number; warehouseId: number; scanner: QrCodeScanner; addPutAwayHistory: (putAwayData: PutAwayRecord) => void; } const style = { position: "absolute", top: "50%", left: "50%", transform: "translate(-50%, -50%)", bgcolor: "background.paper", pt: { xs: 0.5, sm: 1, md: 1.5 }, px: { xs: 1, sm: 1.5, md: 2 }, pb: { xs: 0.5, sm: 1, md: 1.5 }, width: { xs: "95%", sm: "85%", md: "75%", lg: "70%" }, maxWidth: "900px", maxHeight: { xs: "98vh", sm: "95vh", md: "90vh" }, overflow: "hidden", display: "flex", flexDirection: "column", }; const scannerStyle = { position: "absolute", top: "50%", left: "50%", transform: "translate(-50%, -50%)", bgcolor: "background.paper", pt: { xs: 2, sm: 3, md: 4 }, px: { xs: 2, sm: 3, md: 4 }, pb: { xs: 6, sm: 7, md: 8 }, width: { xs: "85%", sm: "70%", md: "60%" }, maxWidth: "600px", }; const PutAwayModal: React.FC = ({ open, onClose, warehouse, stockInLineId, warehouseId, scanner, addPutAwayHistory }) => { const { t } = useTranslation("putAway"); const [serverError, setServerError] = useState(""); const params = useSearchParams(); const [isOpenScanner, setIsOpenScanner] = useState(false); const [firstWarehouseId, setFirstWarehouseId] = useState(null); const [warehouseMismatchError, setWarehouseMismatchError] = useState(""); const [firstWarehouseInfo, setFirstWarehouseInfo] = useState<{name: string, code: string} | null>(null); const [itemDetail, setItemDetail] = useState(); const [totalPutAwayQty, setTotalPutAwayQty] = useState(0); const [unavailableText, setUnavailableText] = useState( undefined, ); const [putQty, setPutQty] = useState(itemDetail?.acceptedQty ?? 0); const [verified, setVerified] = useState(false); const [qtyError, setQtyError] = useState(""); const defaultNewValue = useMemo(() => { // console.log("%c ItemDetail", "color:purple", itemDetail); return ( { ...itemDetail, // status: itemDetail.status ?? "pending", // dnDate: arrayToDateString(itemDetail?.dnDate, "input")?? undefined, // // putAwayLines: dummyPutAwayLine, // // putAwayLines: itemDetail.putAwayLines.map((line) => (return {...line, printQty: 1})) ?? [], // putAwayLines: itemDetail.putAwayLines?.map((line) => ({...line, printQty: 1, _isNew: false})) ?? [], // // qcResult: (itemDetail.qcResult && itemDetail.qcResult?.length > 0) ? itemDetail.qcResult : [],//[...dummyQCData], // escResult: (itemDetail.escResult && itemDetail.escResult?.length > 0) ? itemDetail.escResult : [], productionDate: itemDetail?.productionDate ? arrayToDateString(itemDetail?.productionDate, "input") : undefined, expiryDate: itemDetail?.expiryDate ? arrayToDateString(itemDetail?.expiryDate, "input") : undefined, receiptDate: itemDetail?.receiptDate ? arrayToDateString(itemDetail?.receiptDate, "input") : undefined, acceptQty: itemDetail?.acceptedQty ?? 0, defaultWarehouseId: itemDetail?.defaultWarehouseId ?? 1, } as ModalFormInput ) }, [itemDetail]) const formProps = useForm({ defaultValues: { ...defaultNewValue, }, }); const errors = formProps.formState.errors; useEffect(() => { if (itemDetail) { startScanner(); } }, [itemDetail]) const closeHandler = useCallback>( (...args) => { setVerified(false); setItemDetail(undefined); setTotalPutAwayQty(0); onClose?.(...args); // reset(); }, [onClose], ); const scannerCloseHandler = useCallback>( (...args) => { setIsOpenScanner(false); scanner.stopScan(); console.log("%c Scanning stopped ", "color:cyan"); }, [], ); const startScanner = () => { // setIsOpenScanner(true); scanner.startScan(); console.log("%c Scanning started ", "color:cyan"); }; const openScanner = () => { setIsOpenScanner(true); scanner.startScan(); console.log("%c Scanning started ", "color:cyan"); }; useEffect(() => { if (warehouseId > 0 && firstWarehouseId !== null) { if (warehouseId !== firstWarehouseId) { const firstWh = warehouse.find((w) => w.id == firstWarehouseId); const scannedWh = warehouse.find((w) => w.id == warehouseId); setWarehouseMismatchError("倉庫不匹配!必須使用首次上架的倉庫"); setVerified(false); } else { setWarehouseMismatchError(""); if (scanner.isScanning) { setIsOpenScanner(false); setVerified(true); msg("貨倉掃瞄成功!"); scanner.resetScan(); } } } else if (warehouseId > 0 && firstWarehouseId === null) { // First put away - no validation needed if (scanner.isScanning) { setIsOpenScanner(false); setVerified(true); msg("貨倉掃瞄成功!"); scanner.resetScan(); } } }, [warehouseId, firstWarehouseId]) const warehouseDisplay = useMemo(() => { const targetWarehouseId = firstWarehouseId || warehouseId || 1; const wh = warehouse.find((w) => w.id == warehouseId) ?? warehouse.find((w) => w.id == 1); return <>{wh?.name}
[{wh?.code}]; }, [warehouse, warehouseId, firstWarehouseId,verified]); // useEffect(() => { // Restart scanner for changing warehouse // if (warehouseId > 0) { // scanner.startScan(); // console.log("%c Scanner restarted", "color:cyan"); // } // }, [isOpenScanner]) useLayoutEffect(() => { if (itemDetail !== undefined) { if (itemDetail.status == "received") { formProps.reset({ ...defaultNewValue }) const total = itemDetail.putAwayLines?.reduce((sum, p) => sum + p.qty, 0) ?? 0; setPutQty(itemDetail?.acceptedQty - total); // ✅ Get first warehouse from existing put away lines const firstPutAwayLine = itemDetail.putAwayLines?.[0]; if (firstPutAwayLine?.warehouseId) { setFirstWarehouseId(firstPutAwayLine.warehouseId); // ✅ Store first warehouse info for display const firstWh = warehouse.find((w) => w.id == firstPutAwayLine.warehouseId); if (firstWh) { setFirstWarehouseInfo({ name: firstWh.name || "", code: firstWh.code || "" }); } } else { setFirstWarehouseId(null); setFirstWarehouseInfo(null); } console.log("%c Loaded data:", "color:lime", defaultNewValue); } else { switch (itemDetail.status) { case "pending": alert("此貨品有待品檢"); break; case "rejected": alert("此貨品已被拒收"); break; case "escalated": alert("此貨品已被上報"); break; case "partially_completed": alert("此貨品已部分上架"); break; case "completed": alert("此貨品已上架"); break; default: alert("此貨品暫時無法上架"); break; } closeHandler({}, "backdropClick"); } } }, [open, itemDetail, defaultNewValue]) const fetchStockInLine = useCallback( async (stockInLineId: number) => { setUnavailableText(undefined); try { const res = await fetchStockInLineInfo(stockInLineId); console.log("%c Fetched Stock In Line Info:", "color:gold", res); const total = res.putAwayLines?.reduce((sum, p) => sum + p.qty, 0) ?? 0; setTotalPutAwayQty(total); setItemDetail(res); } catch (e) { console.log("%c Error when fetching Stock In Line: ", "color:red", e); alert("錯誤的二維碼"); closeHandler({}, "backdropClick"); } }, [formProps, itemDetail, fetchStockInLineInfo], ); useEffect(() => { if (stockInLineId) { fetchStockInLine(stockInLineId); } }, [stockInLineId]); const validateQty = useCallback((qty : number = putQty) => { // if (isNaN(putQty) || putQty === undefined || putQty === null || typeof(putQty) != "number") { // setQtyError(t("value must be a number")); // } else if (!Number.isInteger(qty)) { setQtyError(t("value must be integer")); } //if (qty > itemDetail?.demandQty!! - totalPutAwayQty) { //setQtyError(`${t("putQty must not greater than")} ${ // itemDetail?.demandQty!! - totalPutAwayQty}` ); //} if (qty > itemDetail?.acceptedQty!! - totalPutAwayQty) { setQtyError(`${t("putQty must not greater than")} ${ itemDetail?.acceptedQty!! - totalPutAwayQty}` ); } else // if (qty > itemDetail?.acceptedQty!!) { // setQtyError(`${t("putQty must not greater than")} ${ // itemDetail?.acceptedQty}` ); // } else if (qty < 1) { setQtyError(t("minimal value is 1")); } else { setQtyError(""); } console.log("%c Validated putQty:", "color:yellow", putQty); },[setQtyError, putQty, itemDetail]) const onSubmit = useCallback>( async (data, event) => { // console.log("errors", errors); // const lotLine = { // warehouseId: warehouseId, // qty: acceptQty; // } try { if (firstWarehouseId !== null && warehouseId !== firstWarehouseId) { setWarehouseMismatchError("倉庫不匹配!必須使用首次上架的倉庫"); return; } const args = { // ...itemDetail, id: itemDetail?.id, purchaseOrderId: itemDetail?.purchaseOrderId, purchaseOrderLineId: itemDetail?.purchaseOrderLineId, itemId: itemDetail?.itemId, acceptedQty: itemDetail?.acceptedQty, acceptQty: itemDetail?.acceptedQty, status: "received", // purchaseOrderId: parseInt(params.get("id")!), // purchaseOrderLineId: itemDetail?.purchaseOrderLineId, // itemId: itemDetail?.itemId, // acceptedQty: data.acceptedQty, // status: data.status, // ...data, // productionDate: productionDate, // for putaway data inventoryLotLines: [{ warehouseId: warehouseId, qty: putQty, }], // data.putAwayLines?.filter((line) => line._isNew !== false) } as StockInLineEntry & ModalFormInput; console.log(args); // return // if (formProps.formState.errors) { // setServerError(t("An error has occurred. Please try again later.")); // return false; // } if (qtyError !== "") { return; } console.log("%c Submitting Data:", "color:blue", args); const res = await updateStockInLine(args); if (Boolean(res.id)) { // update entries console.log("%c Update Success:", "color:green", res); // add loading const putAwayData = { itemName: itemDetail?.itemName, itemCode: itemDetail?.itemNo, poCode: itemDetail?.poCode, joCode: itemDetail?.joCode, lotNo: itemDetail?.lotNo, warehouseCode: warehouse.find((w) => w.id == warehouseId)?.code, warehouse: warehouse.find((w) => w.id == warehouseId)?.name, putQty: putQty, uom: itemDetail?.uom?.udfudesc, } as PutAwayRecord; addPutAwayHistory(putAwayData); msg("貨品上架成功!"); closeHandler({}, "backdropClick"); } // console.log(res); // if (res) } catch (e) { // server error setServerError(t("An error has occurred. Please try again later.")); console.log(e); } }, [t, itemDetail, putQty, warehouseId, firstWarehouseId], ); return ( {itemDetail != undefined ? ( <> 處理上架 {itemDetail.jobOrderId ? ( ) : ( )} {verified? ( <> 掃瞄完成 ) : ( <> {warehouseMismatchError || (firstWarehouseId !== null && warehouseId > 0 && warehouseId !== firstWarehouseId) ? "倉庫不匹配!請掃瞄首次上架的倉庫" : "請掃瞄倉庫二維碼"} ) } {warehouseDisplay} {verified ? "" : `(建議)`} { const value = e.target.value; validateQty(Number(value)); setPutQty(Number(value)); }} onKeyDown={(e) => { // Prevent non-numeric characters if (["e", "E", "+", "-", "."].includes(e.key)) { e.preventDefault(); } }} // onBlur={(e) => { // const value = e.target.value; // setPutQty(Number(value)); // validateQty(Number(value)); // }} // disabled={true} // {...register("acceptedQty", { // required: "acceptedQty required!", // })} error={qtyError !== ""} helperText={qtyError} /> {/* */} ) : ( // <> {t("scan loading")} )} {t("Please scan warehouse qr code")} {/* */} ); }; export default PutAwayModal;