|
- "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<ModalProps, "children"> {
- 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<Props> = ({ open, onClose, warehouse, stockInLineId, warehouseId, scanner, addPutAwayHistory }) => {
- const { t } = useTranslation("putAway");
- const [serverError, setServerError] = useState("");
- const params = useSearchParams();
-
- const [isOpenScanner, setIsOpenScanner] = useState<boolean>(false);
- const [firstWarehouseId, setFirstWarehouseId] = useState<number | null>(null);
- const [warehouseMismatchError, setWarehouseMismatchError] = useState<string>("");
-
- const [firstWarehouseInfo, setFirstWarehouseInfo] = useState<{name: string, code: string} | null>(null);
-
- const [itemDetail, setItemDetail] = useState<StockInLine>();
- const [totalPutAwayQty, setTotalPutAwayQty] = useState<number>(0);
- const [unavailableText, setUnavailableText] = useState<string | undefined>(
- undefined,
- );
-
- const [putQty, setPutQty] = useState<number>(itemDetail?.acceptedQty ?? 0);
- const [verified, setVerified] = useState<boolean>(false);
- const [qtyError, setQtyError] = useState<string>("");
-
- 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<ModalFormInput>({
- defaultValues: {
- ...defaultNewValue,
- },
- });
- const errors = formProps.formState.errors;
-
- useEffect(() => {
- if (itemDetail) {
- startScanner();
- }
- }, [itemDetail])
-
- const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>(
- (...args) => {
- setVerified(false);
- setItemDetail(undefined);
- setTotalPutAwayQty(0);
- onClose?.(...args);
- // reset();
- },
- [onClose],
- );
-
- const scannerCloseHandler = useCallback<NonNullable<ModalProps["onClose"]>>(
- (...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} <br/> [{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<SubmitHandler<ModalFormInput>>(
- 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 (
- <FormProvider {...formProps}>
- <Modal open={open} onClose={closeHandler}>
- <Box
- sx={style}
- component="form"
- onSubmit={formProps.handleSubmit(onSubmit)}
- >
- <Box sx={{ overflow: "hidden", flex: 1, display: "flex", flexDirection: "column" }}>
- <Grid container xs={12}>
- <Grid item xs={12}>
- {itemDetail != undefined ? (
- <>
- <Stack direction="column" justifyContent="flex-end" gap={0.25} sx={{ mb: 0.5 }}>
- <Typography variant="h4" sx={{ fontSize: { xs: "0.95rem", sm: "1.1rem", md: "1.3rem" }, mb: 0.25, lineHeight: 1.2 }}>
- 處理上架
- </Typography>
- <Box sx={{ "& .MuiFormControl-root": { mb: 0.5 }, "& .MuiTextField-root": { mb: 0.5 }, "& .MuiGrid-item": { mb: 0.25 } }}>
- <Grid item xs={12}>
- {itemDetail.jobOrderId ? (
- <FgStockInForm itemDetail={itemDetail} disabled={true} putawayMode={true}/>
- ) : (
- <StockInForm itemDetail={itemDetail} disabled={true} putawayMode={true}/>
- )}
- </Grid>
- </Box>
- <Paper sx={{ mt: 0.5, padding: { xs: 0.5, sm: 0.75, md: 1 }, width: "100%", backgroundColor: verified ? '#bceb19' : '#FCD34D' }}>
- <Grid
- container
- spacing={{ xs: 0.5, sm: 0.75, md: 1 }}
- direction="row"
- justifyContent="space-between"
- alignItems="stretch"
- sx={{ width: '100%' }}
- >
- <Grid item xs={12} sm={5}>
- <Box sx={{
- mb: { xs: 0.25, md: 0.5 },
- p: { xs: 0.5, sm: 0.75, md: 1 },
- backgroundColor: verified ? '#bceb19' : '#FCD34D',
- borderRadius: 0,
- display: 'flex',
- flexDirection: 'row',
- gap: 0.25,
- flexWrap: 'wrap'
- }}
- >
- <Grid container>
- {verified? (
- <>
- <CheckCircle sx={{
- color:"green",
- fontSize: { xs: "16px", sm: "20px", md: "24px" },
- mr: 0.25
- }}/>
- <Typography
- variant="h5"
- component="h2"
- sx={{
- fontWeight: 'bold',
- color: 'black',
- fontSize: { xs: "0.75rem", sm: "0.9rem", md: "1rem" },
- lineHeight: 1.2
- }}
- noWrap
- >
- 掃瞄完成
- </Typography>
- </>
- ) : (
- <>
- <ErrorOutline sx={{
- color:"red",
- fontSize: { xs: "16px", sm: "20px", md: "24px" },
- mr: 0.25
- }}/>
- <Typography
- variant="h5"
- component="h2"
- sx={{
- fontWeight: 'bold',
- color: 'black',
- fontSize: { xs: "0.75rem", sm: "0.9rem", md: "1rem" },
- lineHeight: 1.2
- }}
- noWrap
- >
- {warehouseMismatchError || (firstWarehouseId !== null && warehouseId > 0 && warehouseId !== firstWarehouseId)
- ? "倉庫不匹配!請掃瞄首次上架的倉庫"
- : "請掃瞄倉庫二維碼"}
- </Typography>
- </>
- )
- }
- <QrCode sx={{ fontSize: { xs: "16px", sm: "20px", md: "24px" } }}/>
- </Grid>
- <Grid container>
- <Typography
- variant="h4"
- sx={{
- fontWeight: 'bold',
- color: 'black',
- fontSize: { xs: "0.85rem", sm: "1rem", md: "1.25rem" },
- lineHeight: 1.2
- }}
- noWrap
- >
- {warehouseDisplay} <Box component="span" sx={{ fontSize: { xs: "0.95rem", sm: "1.2rem", md: "1.5rem" }, color: "black" }}>{verified ? "" : `(建議)`}</Box>
- </Typography>
- </Grid>
-
- </Box>
- </Grid>
- <Grid item xs={12} sm={3}>
- <Box sx={{
- height: '100%',
- p: { xs: 0.25, sm: 0.5, md: 0.75 },
- textAlign: 'center',
- display: "flex",
- flexDirection: "column",
- justifyContent: "center",
- }}>
- <TextField
- type="number"
- label={t("putQty")}
- fullWidth
- size="small"
- sx={{
- flex: 1,
- "& .MuiInputBase-input": {
- padding: { xs: "6px 6px 1px", sm: "8px 8px 2px", md: "10px 10px 3px" },
- fontSize: { xs: "16px", sm: "22px", md: "32px" },
- fontWeight: "bold",
- height: { xs: "32px", sm: "40px", md: "48px" },
- },
- "& .MuiInputBase-root": {
- height: "100%",
- borderColor: "black",
- },
- "& .MuiInputLabel-root": {
- fontSize: { xs: "10px", sm: "12px", md: "18px" },
- top: "-2px",
- color: "black",
- },
- "& .MuiFormHelperText-root": {
- fontSize: { xs: "9px", sm: "10px", md: "14px" },
- marginTop: "2px",
- lineHeight: "1.1",
- },
- }}
- // defaultValue={itemDetail?.demandQty!! - totalPutAwayQty}
- // defaultValue={itemDetail.demandQty}
- defaultValue={itemDetail?.acceptedQty!! - totalPutAwayQty}
- onChange={(e) => {
- 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}
- />
- </Box>
- </Grid>
- <Grid item xs={12} sm={4}>
- <Box
- sx={{
- p: { xs: 0.25, sm: 0.5, md: 0.75 },
- textAlign: 'center',
- height: '100%', // Ensure D stretches to full height
- display: 'flex',
- alignItems: 'center',
- justifyContent: 'center',
- }}>
- <Button
- id="putawaySubmit"
- type="submit"
- variant="contained"
- startIcon={<Check sx={{ fontSize: { xs: "14px", sm: "16px", md: "20px" } }} />}
- color="secondary"
- size="small"
- sx={{
- height: "100%",
- flex: "0 0 auto",
- padding: { xs: "3px 6px", sm: "4px 8px", md: "6px 12px" },
- fontSize: { xs: "9px", sm: "11px", md: "18px" },
- whiteSpace: "nowrap",
- textAlign: "center",
- border: "1.5px solid", // Add outline
- borderColor: "blue",
- minHeight: { xs: "32px", sm: "36px", md: "40px" },
- "&:hover": {
- borderColor: "grey.200", // Slightly different color on hover
- backgroundColor: "secondary.dark", // Darker background on hover
- },
- "&:disabled": {
- borderColor: "grey.400", // Visible outline even when disabled
- opacity: 0.7, // Slightly faded but still visible
- },
- }}
- // onClick={formProps.handleSubmit()}
- disabled={!verified || qtyError != ""}
- >
- {t("confirm putaway")}
- </Button>
- </Box>
- {/* <Button
- id="scanWarehouse"
- variant="contained"
- startIcon={<QrCode />}
- color="primary"
- // sx={{ mx: 3, minWidth : "120px", height: "80px",
- // padding: "12px 12px", fontSize: "24px"}}
- sx={{
- flex: "0 0 auto",
- padding: "8px 16px",
- fontSize: { xs: "16px", sm: "20px", md: "24px" },
- whiteSpace: "nowrap",
- textAlign: "center",}}
- onClick={openScanner}>
-
- {t("scan warehouse")}
- </Button> */}
- </Grid>
- </Grid>
- </Paper>
- </Stack>
- </>
- ) : (
- // <ReactQrCodeScanner scannerConfig={scannerConfig} />
- <>
- <Typography variant="h4" sx={{ fontSize: { xs: "0.95rem", sm: "1.1rem", md: "1.3rem" } }}>
- {t("scan loading")}
- </Typography>
- <LoadingComponent/>
- </>
- )}
- </Grid>
- </Grid>
- </Box>
- <Modal open={isOpenScanner} onClose={scannerCloseHandler}>
- <Box sx={scannerStyle}>
- <Typography variant="h4" sx={{
- display: 'flex',
- flexDirection: 'column',
- justifyContent: 'center',
- margin: 0,
- alignItems: 'center',
- textAlign: 'center',
- fontSize: { xs: "0.95rem", sm: "1.1rem", md: "1.3rem" }
- }}
- >
- {t("Please scan warehouse qr code")}
- </Typography>
- {/* <ReactQrCodeScanner scannerConfig={scannerConfig} /> */}
- </Box>
- </Modal>
-
- </Box>
- </Modal>
- </FormProvider>
- );
- };
-
- export default PutAwayModal;
|