Browse Source

update qc

master
kelvinsuen 1 month ago
parent
commit
331a47ab78
13 changed files with 1214 additions and 857 deletions
  1. +12
    -1
      src/app/api/qc/actions.ts
  2. +37
    -10
      src/app/api/qc/index.ts
  3. +3
    -8
      src/app/api/stockIn/index.ts
  4. +1
    -1
      src/components/JoSearch/JoSearch.tsx
  5. +27
    -28
      src/components/PoDetail/PoInputGrid.tsx
  6. +0
    -482
      src/components/PoDetail/PoQcStockInModal.tsx
  7. +1
    -1
      src/components/PoDetail/PutAwayForm.tsx
  8. +1
    -1
      src/components/PoDetail/QCDatagrid.tsx
  9. +2
    -2
      src/components/PoDetail/QcFormOld.tsx
  10. +216
    -323
      src/components/Qc/QcComponent.tsx
  11. +245
    -0
      src/components/Qc/QcForm.tsx
  12. +669
    -0
      src/components/Qc/QcStockInModal.tsx
  13. +0
    -0
      src/components/Qc/dummyQcTemplate.tsx

+ 12
- 1
src/app/api/qc/actions.ts View File

@@ -5,8 +5,9 @@ import { revalidateTag } from "next/cache";
import { cache } from "react";
//import { serverFetchJson } from "@/app/utils/fetchUtil";
import { serverFetchJson } from "../../utils/fetchUtil";
import { QcItemWithChecks } from ".";
import { QcCategory, QcItemWithChecks } from ".";

// DEPRECIATED
export interface QcResult {
id: number;
qcItemId: number;
@@ -43,6 +44,16 @@ export const fetchQcItemCheck = cache(async (itemId?: number) => {
});
});

export const fetchQcCategory = cache(async (itemId: number, type: string = "IQC") => {
const params = new URLSearchParams({
itemId: itemId.toString(),
type,
}).toString();
return serverFetchJson<QcCategory>(`${BASE_API_URL}/qcCategories/items?${params}`, {
next: { tags: ["qc"] },
});
});

export const fetchQcResult = cache(async (id: number) => {
return serverFetchJson<QcResult[]>(`${BASE_API_URL}/qcResult/${id}`, {
next: { tags: ["qc"] },


+ 37
- 10
src/app/api/qc/index.ts View File

@@ -15,23 +15,50 @@ export interface QcItemWithChecks {
description: string | undefined;
}

export interface QcResult{
export interface QcCategory {
id: number,
code?: string,
name?: string,
description?: string,
qcItems: QcData[],
}
export interface QcData {
id?: number,
qcItemId: number,
code?: string,
name?: string,
order?: number,
description?: string,
// qcPassed: boolean | undefined
// failQty: number | undefined
// remarks: string | undefined
}
export interface QcResult extends QcData{
id?: number;
qcItemId: number;
qcPassed?: boolean;
failQty?: number;
remarks?: string;
escalationLogId?: number;
stockInLineId?: number;
stockOutLineId?: number;
}
export interface QcData {
id?: number,
qcItemId: number,
code: string,
name: string,
description: string,
qcPassed: boolean | undefined
failQty: number | undefined
remarks: string | undefined
export interface QcInput {
id: number;
itemId: number;
acceptedQty: number;
demandQty: number;
status?: string;

jobOrderId: number;
purchaseOrderId?: number;
purchaseOrderLineId: number;
}
export interface QcFormInput {
acceptQty: number;
qcAccept: boolean;
qcDecision?: number;
qcResult: QcResult[];
}

export const fetchQcItemCheckList = cache(async () => {


+ 3
- 8
src/app/api/stockIn/index.ts View File

@@ -6,7 +6,7 @@ import { serverFetchJson } from "../../utils/fetchUtil";
import { BASE_API_URL } from "../../../config/api";
import { Uom } from "../settings/uom";
import { RecordsRes } from "../utils";
import { QcResult } from "../qc";
import { QcFormInput, QcResult } from "../qc";
import { EscalationResult } from "../escalation";

export enum StockInStatus {
@@ -156,14 +156,9 @@ export interface PutAwayInput {
warehouseId: number;
putAwayLines: PutAwayLine[]
}
export interface QcInput {
acceptQty: number;
qcAccept: boolean;
qcDecision?: number;
qcResult: QcResult[];
}

export type ModalFormInput = Partial<
QcInput & StockInInput & PutAwayInput
QcFormInput & StockInInput & PutAwayInput
> & {
escalationLog? : Partial<EscalationInput>
};

+ 1
- 1
src/components/JoSearch/JoSearch.tsx View File

@@ -16,7 +16,7 @@ import { Button, Stack } from "@mui/material";
import { BomCombo } from "@/app/api/bom";
import JoCreateFormModal from "./JoCreateFormModal";
import AddIcon from '@mui/icons-material/Add';
import QcStockInModal from "../PoDetail/QcStockInModal";
import QcStockInModal from "../Qc/QcStockInModal";
import { useSession } from "next-auth/react";
import { SessionWithTokens } from "@/config/authConfig";
import { createStockInLine } from "@/app/api/stockIn/actions";


+ 27
- 28
src/components/PoDetail/PoInputGrid.tsx View File

@@ -57,11 +57,10 @@ import QrCodeIcon from "@mui/icons-material/QrCode";
import { downloadFile } from "@/app/utils/commonUtil";
import { fetchPoQrcode } from "@/app/api/pdf/actions";
import { fetchQcResult } from "@/app/api/qc/actions";
import PoQcStockInModal from "./PoQcStockInModal";
import DoDisturbIcon from "@mui/icons-material/DoDisturb";
import { useSession } from "next-auth/react";
// import { SessionWithTokens } from "src/config/authConfig";
import QcStockInModal from "./QcStockInModal";
import QcStockInModal from "../Qc/QcStockInModal";
import { decimalFormatter, integerFormatter } from "@/app/utils/formatUtil";
import { PrinterCombo } from "@/app/api/settings/printer";
import { EscalationResult } from "@/app/api/escalation";
@@ -256,31 +255,31 @@ function PoInputGrid({
return await fetchQcResult(stockInLineId as number);
}, []);

const handleQC = useCallback( // UNUSED NOW!
(id: GridRowId, params: any) => async () => {
setBtnIsLoading(true);
setRowModesModel((prev) => ({
...prev,
[id]: { mode: GridRowModes.View },
}));
const qcResult = await fetchQcDefaultValue(id);
// console.log(params.row);
console.log("Fetched QC Result:", qcResult);
// const handleQC = useCallback( // UNUSED NOW!
// (id: GridRowId, params: any) => async () => {
// setBtnIsLoading(true);
// setRowModesModel((prev) => ({
// ...prev,
// [id]: { mode: GridRowModes.View },
// }));
// const qcResult = await fetchQcDefaultValue(id);
// // console.log(params.row);
// console.log("Fetched QC Result:", qcResult);
setModalInfo({
...params.row,
qcResult: qcResult,
});
// set default values
setTimeout(() => {
// open qc modal
console.log("delayed");
openQcModal();
setBtnIsLoading(false);
}, 200);
},
[fetchQcDefaultValue, openQcModal],
);
// setModalInfo({
// ...params.row,
// qcResult: qcResult,
// });
// // set default values
// setTimeout(() => {
// // open qc modal
// console.log("delayed");
// openQcModal();
// setBtnIsLoading(false);
// }, 200);
// },
// [fetchQcDefaultValue, openQcModal],
// );

const [newOpen, setNewOpen] = useState(false);
const stockInLineId = searchParams.get("stockInLineId");
@@ -326,7 +325,7 @@ const closeNewModal = useCallback(() => {
// setTimeout(() => {
// }, 200);
},
[fetchQcDefaultValue, openNewModal, pathname, router, searchParams]
[openNewModal, pathname, router, searchParams]
);

// Open modal if `stockInLineId` exists in the URL
@@ -793,7 +792,7 @@ const closeNewModal = useCallback(() => {
},
},
],
[t, handleStart, handleQC, handleEscalation, handleStockIn, handlePutAway, handleDelete, handleReject, itemDetail],
[t, handleStart, handleEscalation, handleStockIn, handlePutAway, handleDelete, handleReject, itemDetail],
);

const unsortableColumns = useMemo(() =>


+ 0
- 482
src/components/PoDetail/PoQcStockInModal.tsx View File

@@ -1,482 +0,0 @@
"use client";

import {
ModalFormInput,
PurchaseQCInput,
PurchaseQcResult,
StockInInput,
StockInLineEntry,
updateStockInLine,
} from "@/app/api/po/actions";
import { Box, Button, Modal, ModalProps, Stack } from "@mui/material";
import {
Dispatch,
SetStateAction,
useCallback,
useContext,
useEffect,
useMemo,
useState,
} from "react";
import { FormProvider, SubmitHandler, useForm } from "react-hook-form";
import { useTranslation } from "react-i18next";
import QcForm from "./QcForm";
import { QcItemWithChecks } from "@/app/api/qc";
import { Check, CurrencyYuanRounded, TtyTwoTone } from "@mui/icons-material";
import { PurchaseOrderLine, StockInLine } from "@/app/api/po";
import { useSearchParams } from "next/navigation";
import { StockInLineRow } from "./PoInputGrid";
import EscalationForm from "./EscalationForm";
import StockInFormOld from "./StockInFormOld";
import PutAwayForm from "./PutAwayForm";
import {
INPUT_DATE_FORMAT,
stockInLineStatusMap,
} from "@/app/utils/formatUtil";
import dayjs from "dayjs";
import arraySupport from "dayjs/plugin/arraySupport";
import { downloadFile } from "@/app/utils/commonUtil";
import { fetchPoQrcode } from "@/app/api/pdf/actions";
import UploadContext from "../UploadProvider/UploadProvider";
import useUploadContext from "../UploadProvider/useUploadContext";
import RejectForm from "./RejectForm";
import { isNullOrUndefined } from "html5-qrcode/esm/core";
import { isEmpty, isFinite } from "lodash";

dayjs.extend(arraySupport);
interface CommonProps extends Omit<ModalProps, "children"> {
// setRows: Dispatch<SetStateAction<PurchaseOrderLine[]>>;
setEntries?: Dispatch<SetStateAction<StockInLineRow[]>>;
setStockInLine?: Dispatch<SetStateAction<StockInLine[]>>;
itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] };
setItemDetail: Dispatch<
SetStateAction<
| (StockInLine & {
warehouseId?: number;
})
| undefined
>
>;
qc?: QcItemWithChecks[];
warehouse?: any[];
type: "qc" | "stockIn" | "escalation" | "putaway" | "reject";
}
interface QcProps extends CommonProps {
qc: QcItemWithChecks[];
type: "qc";
}
interface StockInProps extends CommonProps {
// naming
type: "stockIn";
}
interface PutawayProps extends CommonProps {
warehouse: any[];
type: "putaway";
}
interface EscalationProps extends CommonProps {
// naming
type: "escalation";
}
interface RejectProps extends CommonProps {
// naming
type: "reject";
}

type Props =
| QcProps
| StockInProps
| PutawayProps
| EscalationProps
| RejectProps;
const style = {
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
overflow: "scroll",
bgcolor: "background.paper",
pt: 5,
px: 5,
pb: 10,
display: "block",
width: { xs: "60%", sm: "60%", md: "60%" },
};

const PoQcStockInModal: React.FC<Props> = ({
type,
// setRows,
setEntries,
setStockInLine,
open,
onClose,
itemDetail,
setItemDetail,
qc,
warehouse,
}) => {
const { setIsUploading } = useUploadContext();

const [serverError, setServerError] = useState("");
const { t } = useTranslation("purchaseOrder");
const params = useSearchParams();
const [btnIsLoading, setBtnIsLoading] = useState(false);

// console.log(params.get("id"));
// console.log(itemDetail);
// console.log(itemDetail.qcResult);
const formProps = useForm<ModalFormInput>({
defaultValues: {
...itemDetail,
// receiptDate: itemDetail.receiptDate || dayjs().add(-1, "month").format(INPUT_DATE_FORMAT),
// warehouseId: itemDetail.defaultWarehouseId || 0
},
});
// console.log(formProps);
const errors = formProps.formState.errors;
const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>(
(...args) => {
onClose?.(...args);
// reset();
},
[onClose],
);

useEffect(() => {
// setDefaultValues({...itemDetail});
if (!itemDetail) {
console.log(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 accQty = formProps.watch("acceptedQty");
useEffect(() => {
formProps.clearErrors("acceptedQty")
}, [accQty])

const productLotNo = formProps.watch("productLotNo");
const checkStockIn = useCallback(
(data: ModalFormInput): boolean => {
let hasErrors = false;
if (accQty! <= 0 ) {
formProps.setError("acceptedQty", {
message: `${t("Accepted qty must greater than")} ${
0
}`,
type: "required",
});
hasErrors = true;
} else if (accQty! > itemDetail.acceptedQty) {
formProps.setError("acceptedQty", {
message: `${t("Accepted qty must not greater than")} ${
itemDetail.acceptedQty
}`,
type: "required",
});
hasErrors = true;
}

if (isEmpty(productLotNo)) {
formProps.setError("productLotNo", {
message: `${t("Product Lot No must not be empty")}`,
type: "required",
});
hasErrors = true;
}

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;
},
[accQty, itemDetail.acceptedQty, itemDetail.shelfLife, productLotNo, formProps, t],
);

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>>(
async (data, event) => {
setBtnIsLoading(true);
setIsUploading(true);
formProps.clearErrors();
let hasErrors = false;
console.log(errors);
console.log(data);
console.log(itemDetail);
// console.log(fix0IndexedDate(data.receiptDate));
try {
// add checking
if (type === "stockIn") {
hasErrors = checkStockIn(data);
console.log(hasErrors);
}
if (type === "putaway") {
hasErrors = checkPutaway(data);
console.log(hasErrors);
}
//////////////////////// modify this mess later //////////////////////
let productionDate = null;
let expiryDate = null;
let receiptDate = null;
let acceptedQty = null;
if (data.productionDate) {
productionDate = dayjs(data.productionDate).format(INPUT_DATE_FORMAT);
}
if (data.expiryDate) {
expiryDate = dayjs(data.expiryDate).format(INPUT_DATE_FORMAT);
}
if (data.receiptDate) {
receiptDate = dayjs(data.receiptDate).format(INPUT_DATE_FORMAT);
}
// if ()
if (data.qcResult) {
acceptedQty =
itemDetail.acceptedQty -
data.qcResult.reduce((acc, curr) => acc + curr.failQty, 0);
}
const args = {
id: itemDetail.id,
purchaseOrderId: parseInt(params.get("id")!),
purchaseOrderLineId: itemDetail.purchaseOrderLineId,
itemId: itemDetail.itemId,
...data,
productionDate: productionDate,
expiryDate: expiryDate,
receiptDate: receiptDate,
} as StockInLineEntry & ModalFormInput;
//////////////////////////////////////////////////////////////////////
if (hasErrors) {
console.log(args);
setServerError(t("An error has occurred. Please try again later."));
setBtnIsLoading(false);
setIsUploading(false);
return;
}
console.log(args);
// setBtnIsLoading(false);
// setIsUploading(false)
// return
const res = await updateStockInLine(args);
if (Boolean(res.id)) {
// update entries
const newEntries = res.entity as StockInLine[];
console.log(newEntries);
if (setEntries) {
setEntries((prev) => {
const updatedEntries = [...prev]; // Create a new array
newEntries.forEach((item) => {
const index = updatedEntries.findIndex((p) => p.id === item.id);
if (index !== -1) {
// Update existing item
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
updatedEntries.push(item);
}
});
return updatedEntries; // Return the new array
});
}
// add loading
setBtnIsLoading(false);
setIsUploading(false);
setItemDetail(undefined);
closeHandler({}, "backdropClick");
}
console.log(res);
// if (res)
} catch (e) {
// server error
setBtnIsLoading(false);
setIsUploading(false);
setServerError(t("An error has occurred. Please try again later."));
console.log(e);
}
},
[setIsUploading, formProps, errors, itemDetail, type, params, checkStockIn, checkPutaway, t, setEntries, setStockInLine, setItemDetail, closeHandler],
);

const printQrcode = useCallback(async () => {
setBtnIsLoading(true);
setIsUploading(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);
setIsUploading(false);
}, [setIsUploading, itemDetail.id]);

const renderSubmitButton = useMemo((): boolean => {
if (itemDetail) {
const status = itemDetail.status;
console.log(status);
switch (type) {
case "qc":
return (
stockInLineStatusMap[status] >= 1 &&
stockInLineStatusMap[status] <= 2
);
case "escalation":
return (
stockInLineStatusMap[status] === 1 ||
stockInLineStatusMap[status] >= 3 ||
stockInLineStatusMap[status] <= 5
);
case "stockIn":
return (
stockInLineStatusMap[status] >= 3 &&
stockInLineStatusMap[status] <= 6
);
case "putaway":
return stockInLineStatusMap[status] === 7;
case "reject":
return (
stockInLineStatusMap[status] >= 1 &&
stockInLineStatusMap[status] <= 6
);
default:
return false; // Handle unexpected type
}
} else return false;
}, [type, itemDetail]);
// useEffect(() => {
// console.log(renderSubmitButton)
// }, [renderSubmitButton])

return (
<>
<FormProvider {...formProps}>
<Modal open={open} onClose={closeHandler} sx={{ overflow: "scroll" }}>
<Box
sx={style}
component="form"
onSubmit={formProps.handleSubmit(onSubmit)}
>
{itemDetail !== undefined && type === "qc" && (
<QcForm
qc={qc!}
itemDetail={itemDetail}
disabled={!renderSubmitButton}
/>
)}
{itemDetail !== undefined && type === "escalation" && (
<EscalationForm
itemDetail={itemDetail}
disabled={!renderSubmitButton}
/>
)}
{itemDetail !== undefined && type === "stockIn" && (
<StockInFormOld
itemDetail={itemDetail}
disabled={!renderSubmitButton}
/>
)}
{itemDetail !== undefined && type === "putaway" && (
<PutAwayForm
itemDetail={itemDetail}
warehouse={warehouse!}
disabled={!renderSubmitButton}
/>
)}
{itemDetail !== undefined && type === "reject" && (
<RejectForm
itemDetail={itemDetail}
disabled={!renderSubmitButton}
/>
)}
<Stack direction="row" justifyContent="flex-end" gap={1}>
{renderSubmitButton ? (
<Button
name="submit"
variant="contained"
startIcon={<Check />}
type="submit"
disabled={btnIsLoading}
>
{t("submit")}
</Button>
) : undefined}
{itemDetail !== undefined && type === "putaway" && (
<Button
name="print"
variant="contained"
// startIcon={<Check />}
onClick={printQrcode}
disabled={btnIsLoading}
>
{t("print")}
</Button>
)}
</Stack>
</Box>
</Modal>
</FormProvider>
</>
);
};
export default PoQcStockInModal;

+ 1
- 1
src/components/PoDetail/PutAwayForm.tsx View File

@@ -53,7 +53,7 @@ import { QrCodeInfo } from "@/app/api/qrcode";
import { useQrCodeScannerContext } from "../QrCodeScannerProvider/QrCodeScannerProvider";
import dayjs from "dayjs";
import arraySupport from "dayjs/plugin/arraySupport";
import { dummyPutAwayLine } from "./dummyQcTemplate";
import { dummyPutAwayLine } from "../Qc/dummyQcTemplate";
import { GridRowModesModel } from "@mui/x-data-grid";
dayjs.extend(arraySupport);



+ 1
- 1
src/components/PoDetail/QCDatagrid.tsx View File

@@ -37,7 +37,7 @@ import {
GridApiCommunity,
GridSlotsComponentsProps,
} from "@mui/x-data-grid/internals";
import { dummyQCData } from "./dummyQcTemplate";
import { dummyQCData } from "../Qc/dummyQcTemplate";
// T == CreatexxxInputs map of the form's fields
// V == target field input inside CreatexxxInputs, e.g. qcChecks: ItemQc[], V = ItemQc
// E == error


src/components/PoDetail/QcForm.tsx → src/components/PoDetail/QcFormOld.tsx View File

@@ -50,7 +50,7 @@ type EntryError =

type PoQcRow = TableRow<Partial<PurchaseQcResult>, EntryError>;
// fetchQcItemCheck
const QcForm: React.FC<Props> = ({ qc, itemDetail, disabled }) => {
const QcFormOld: React.FC<Props> = ({ qc, itemDetail, disabled }) => {
const { t } = useTranslation("purchaseOrder");
const apiRef = useGridApiRef();
const {
@@ -313,4 +313,4 @@ const QcForm: React.FC<Props> = ({ qc, itemDetail, disabled }) => {
</Grid>
);
};
export default QcForm;
export default QcFormOld;

src/components/PoDetail/QcComponent.tsx → src/components/Qc/QcComponent.tsx View File

@@ -1,24 +1,9 @@
"use client";

import { QcResult, QCInput } from "@/app/api/stockIn/actions";
import {
Box,
Card,
CardContent,
Checkbox,
Collapse,
FormControl,
FormControlLabel,
Grid,
Radio,
RadioGroup,
Stack,
Tab,
Tabs,
TabsProps,
TextField,
Tooltip,
Typography,
Box, Card, CardContent, Checkbox, Collapse, FormControl,
FormControlLabel, Grid, Radio, RadioGroup, Stack, Tab,
Tabs, TabsProps, TextField, Tooltip, Typography,
} from "@mui/material";
import { useFormContext, Controller, FieldPath } from "react-hook-form";
import { useTranslation } from "react-i18next";
@@ -36,31 +21,32 @@ import {
} from "@mui/x-data-grid";
import InputDataGrid from "../InputDataGrid";
import { TableRow } from "../InputDataGrid/InputDataGrid";
import TwoLineCell from "./TwoLineCell";
import QcSelect from "./QcSelect";
import TwoLineCell from "../PoDetail/TwoLineCell";
import QcSelect from "../PoDetail/QcSelect";
import { GridEditInputCell } from "@mui/x-data-grid";
import { ModalFormInput, StockInLine } from "@/app/api/stockIn";
import { stockInLineStatusMap } from "@/app/utils/formatUtil";
import { fetchQcItemCheck, fetchQcResult } from "@/app/api/qc/actions";
import { QcItemWithChecks, QcData } from "@/app/api/qc";
import { fetchQcCategory, fetchQcResult } from "@/app/api/qc/actions";
import { QcCategory, QcData, QcInput, QcFormInput, QcResult } from "@/app/api/qc";
import axios from "@/app/(main)/axios/axiosInstance";
import { NEXT_PUBLIC_API_URL } from "@/config/api";
import axiosInstance from "@/app/(main)/axios/axiosInstance";
import EscalationComponent from "./EscalationComponent";
import QcDataGrid from "./QCDatagrid";
import EscalationComponent from "../PoDetail/EscalationComponent";
import QcDataGrid from "../PoDetail/QCDatagrid";
import { dummyEscalationHistory,
dummyQcData_A1, dummyQcData_E1, dummyQcData_E2,
dummyQcHeader_A1, dummyQcHeader_E1, dummyQcHeader_E2 } from "./dummyQcTemplate";
import { escape, isNull, min } from "lodash";
import { escape, isNull, min, template } from "lodash";
import { PanoramaSharp } from "@mui/icons-material";
import EscalationLogTable from "../DashboardPage/escalation/EscalationLogTable";
import { EscalationResult } from "@/app/api/escalation";
import { EscalationCombo } from "@/app/api/user";
import { fetchEscalationLogsByStockInLines } from "@/app/api/escalation/actions";
import CollapsibleCard from "../CollapsibleCard/CollapsibleCard";
import LoadingComponent from "../General/LoadingComponent";
import QcForm from "./QcForm";

interface Props {
itemDetail: StockInLine;
itemDetail: QcInput;
// itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] };
// qc: QcItemWithChecks[];
disabled: boolean;
@@ -74,7 +60,7 @@ type EntryError =
}
| undefined;

type QcRow = TableRow<Partial<QcData>, EntryError>;
type QcRow = TableRow<Partial<QcResult>, EntryError>;
// fetchQcItemCheck
const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => {
const { t } = useTranslation("purchaseOrder");
@@ -90,7 +76,7 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => {
resetField,
setError,
clearErrors,
} = useFormContext<QCInput>();
} = useFormContext<QcFormInput>();
const [tabIndex, setTabIndex] = useState(0);
const [rowSelectionModel, setRowSelectionModel] = useState<GridRowSelectionModel>();
@@ -98,6 +84,8 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => {
const qcAccept = watch("qcAccept");
const qcDecision = watch("qcDecision"); //WIP
// const qcResult = useMemo(() => [...watch("qcResult")], [watch("qcResult")]);
const [qcCategory, setQcCategory] = useState<QcCategory>();

const qcRecord = useMemo(() => { // Need testing
const value = watch('qcResult'); //console.log("%c QC update!", "color:green", value);
@@ -105,30 +93,19 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => {
}, [watch('qcResult')]);
const [qcHistory, setQcHistory] = useState<QcResult[]>([]);
const [qcResult, setQcResult] = useState<QcResult[]>([]);
const [newQcData, setNewQcData] = useState<QcResult[]>([]);

const detailMode = useMemo(() => {
const isDetailMode = itemDetail.status == "escalated" || isNaN(itemDetail.jobOrderId);
return isDetailMode;
}, [itemDetail]);
const [escResult, setEscResult] = useState<EscalationResult[]>([]);

// const [qcAccept, setQcAccept] = useState(true);
// const [qcItems, setQcItems] = useState(dummyQCData)

const column = useMemo<GridColDef[]>(
() => [
{
field: "escalation",
headerName: t("escalation"),
flex: 1,
},
{
field: "supervisor",
headerName: t("supervisor"),
flex: 1,
},
], []
)
const qcDisabled = (row : QcResult) => {
return disabled || isExist(row.escalationLogId);
};

const isExist = (data : string | number | undefined) => {
return (data !== null && data !== undefined);
}

const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
(_e, newValue) => {
setTabIndex(newValue);
@@ -136,12 +113,23 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => {
[],
);

const isExist = (data : string | number | undefined) => {
return (data !== null && data !== undefined);
}
const qcType = useMemo(() => {
if (itemDetail) {
const d = itemDetail;
if (isExist(d.jobOrderId)) {
return "EPQC";
}
}
return "IQC"; // Default
}, [itemDetail]);

const detailMode = useMemo(() => {
const isDetailMode = itemDetail.status == "escalated" || isExist(itemDetail.jobOrderId);
return isDetailMode;
}, [itemDetail]);

// W I P //
const validateFieldFail = (field : FieldPath<QCInput>, condition: boolean, message: string) : boolean => {
const validateFieldFail = (field : FieldPath<QcFormInput>, condition: boolean, message: string) : boolean => {
// console.log("Checking if " + message)
if (condition) { setError(field, { message: message}); return false; }
else { clearErrors(field); return true; }
@@ -166,8 +154,8 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => {
} else
console.log("%c Validated accQty:", "color:yellow", accQty);
}
},[setError, qcDecision, accQty, itemDetail])

},[setError, qcDecision, accQty, itemDetail])
useEffect(() => { // W I P // -----
if (qcDecision == 1) {
if (validateFieldFail("acceptQty", accQty > itemDetail.acceptedQty, `${t("acceptQty must not greater than")} ${
@@ -213,208 +201,153 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => {
[],
);

function BooleanEditCell(params: GridRenderEditCellParams) {
const apiRef = useGridApiContext();
const { id, field, value } = params;

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
apiRef.current.setEditCellValue({ id, field, value: e.target.checked });
apiRef.current.stopCellEditMode({ id, field }); // commit immediately
};

return <Checkbox checked={!!value} onChange={handleChange} sx={{ p: 0 }} />;
}

const qcDisabled = (row : QcResult) => {
return disabled || isExist(row.escalationLogId);
};

const qcColumns: GridColDef[] = useMemo(() => [
{
field: "name",
headerName: t("qcItem"),
wrapText: true,
flex: 2.5,
renderCell: (params) => {
const index = params.api.getRowIndexRelativeToVisibleRows(params.id) + 1;
return (
<Box
sx={{
lineHeight: 1.5,
padding: "4px",
fontSize: 18,
}}
>
<b>{`${index}. ${params.value}`}</b><br/>
{params.row.description}
</Box>
)},
},
{
field: 'qcResult',
headerName: t("qcResult"),
flex: 1,
renderCell: (params) => {
const rowValue = params.row;
const index = Number(params.id);//params.api.getRowIndexRelativeToVisibleRows(params.id);
// console.log(rowValue.row);
return (
<FormControl>
<RadioGroup
row
aria-labelledby="demo-radio-buttons-group-label"
// defaultValue={""}
value={rowValue.qcPassed === undefined ? "" : (rowValue.qcPassed ? "true" : "false")}
onChange={(e) => {
const value = (e.target.value === "true");
// setQcItems((prev) =>
// prev.map((r): QcData => (r.id === params.id ? { ...r, qcPassed: value === "true" } : r))
// );
setValue(`qcResult.${index}.qcPassed`, value);
}}
name={`qcPassed-${params.id}`}
>
<FormControlLabel
value="true"
control={<Radio />}
label="合格"
disabled={qcDisabled(rowValue)}
sx={{
color: rowValue.qcPassed === true ? "green" : "inherit",
"& .Mui-checked": {color: "green"}
}}
/>
<FormControlLabel
value="false"
control={<Radio />}
label="不合格"
disabled={qcDisabled(rowValue)}
sx={{
color: rowValue.qcPassed === false ? "red" : "inherit",
"& .Mui-checked": {color: "red"}
}}
/>
</RadioGroup>
</FormControl>
);
},
},
{
field: "failQty",
headerName: t("failedQty"),
flex: 0.5,
// editable: true,
renderCell: (params) => {
const index = Number(params.id);//params.api.getRowIndexRelativeToVisibleRows(params.id);
return (
<TextField
type="number"
value={!params.row.qcPassed? params.value : '0'}
disabled={params.row.qcPassed || qcDisabled(params.row)}
/* TODO improve */
/* Reference: https://grok.com/share/c2hhcmQtNA%3D%3D_10787069-3eec-40af-a7cc-bacbdb86bf05 */
onChange={(e) => {
const v = e.target.value;
const next = v === '' ? undefined : Number(v);
if (Number.isNaN(next)) return;
setValue(`qcResult.${index}.failQty`, next);
}}
// onBlur={(e) => {
// const v = e.target.value;
// const next = v === '' ? undefined : Number(v);
// if (Number.isNaN(next)) return;
// setValue(`qcResult.${index}.failQty`, next);
// }}
onClick={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
inputProps={{ min: 0 }}
sx={{ width: '100%',
"& .MuiInputBase-input": {
padding: "0.75rem",
fontSize: 24,
},
}}
/>
);
},
},
{
field: "remarks",
headerName: t("remarks"),
flex: 2,
renderCell: (params) => {
const index = Number(params.id);//params.api.getRowIndexRelativeToVisibleRows(params.id);
return (
<TextField
size="small"
defaultValue={params.value}
disabled={qcDisabled(params.row)}
onBlur={(e) => {
const value = e.target.value;
setValue(`qcResult.${index}.remarks`, value);
}}
// onChange={(e) => {
// const remarks = e.target.value;
// // const next = v === '' ? undefined : Number(v);
// // if (Number.isNaN(next)) return;
// // setQcItems((prev) =>
// // prev.map((r) => (r.id === params.id ? { ...r, remarks: remarks } : r))
// // );
// }}
// {...register(`qcResult.${index}.remarks`, {
// required: "remarks required!",
// })}
onClick={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
sx={{ width: '100%',
"& .MuiInputBase-input": {
padding: "0.75rem",
fontSize: 24,
},
}}
/>
);
},
},
], [])

// Set initial value for acceptQty
useEffect(() => {
if (itemDetail?.demandQty > 0) { //!== undefined) {
setValue("acceptQty", itemDetail.demandQty); // THIS NEED TO UPDATE TO NOT USE DEMAND QTY
setValue("acceptQty", itemDetail.demandQty); // TODO: THIS NEED TO UPDATE TO NOT USE DEMAND QTY
} else {
setValue("acceptQty", itemDetail?.acceptedQty);
}
}, [itemDetail?.demandQty, itemDetail?.acceptedQty, setValue]);

// Fetch Qc Data
useEffect(() => {
console.log("%c Qc Record updated:", "color:green", qcRecord);
if (qcRecord.length < 1) { // New QC
const fetchedQcData = dummyQcData; //TODO fetch from DB
setValue("qcResult", fetchedQcData);
} else {
if (itemDetail?.status == "escalated") { // Copy the previous QC data for editing
if (qcRecord.find((qc) => !isExist(qc.escalationLogId)) === undefined) {
const copiedQcData = qcRecord.map(qc => ({ ...qc, escalationLogId: undefined }));
const mutableQcData = [...qcRecord, ...copiedQcData];
setValue("qcResult", mutableQcData);
}
// console.log("%c QC ItemDetail updated:", "color: gold", itemDetail);
if (itemDetail) {
const d = itemDetail;
fetchNewQcData(d);
if (d.status == "pending") {
//
} else {
fetchQcResultData(d);
}
}
}, [itemDetail]);

const fetchNewQcData = useCallback(
async (input: QcInput) => {
try {
const res = await fetchQcCategory(input.itemId, qcType);
if (res.qcItems.length > 0) {
console.log("%c Fetched Qc Template: ", "color:orange", res);

setQcCategory(res);
// setQcResult(res.qcItems);
// setValue("qcResult", res.qcItems);
} else throw("Result is undefined");
} catch (e) {
console.log("%c Error when fetching Qc Template: ", "color:red", e);
alert(t("Missing QC Template, please contact administrator"));
// closeHandler({}, "backdropClick");
}
},[fetchQcCategory, setValue]
);

const fetchQcResultData = useCallback(
async (input: QcInput) => {
try {
const res = await fetchQcResult(input.id); // StockInLineId for now
if (res.length > 0) {
console.log("%c Fetched Qc Result: ", "color:orange", res);
setValue("qcResult", res);

fetchEscalationLogData(input.id);
// } else {setStockInLineInfo((prev) => ({...prev, qcResult: []} as StockInLine));}
} else throw("Result is undefined");
} catch (e) {
console.log("%c Error when fetching Qc Result: ", "color:red", e);
// alert("Something went wrong, please retry");
// closeHandler({}, "backdropClick");
}
},[fetchQcResult, setValue]
);

const fetchEscalationLogData = useCallback(
async (stockInLineId: number) => {
try {
const res = await fetchEscalationLogsByStockInLines([stockInLineId]);
if (res.length > 0) {
console.log("%c Fetched Escalation Log: ", "color:orange", res[0]);
setEscResult(res);
// formProps.setValue("escalationLog", res[0]);

}// else throw("Result is undefined");
} catch (e) {
console.log("%c Error when fetching EscalationLog: ", "color:red", e);
// alert("Something went wrong, please retry");
// closeHandler({}, "backdropClick");
}
},[fetchEscalationLogsByStockInLines]
);

if (qcRecord.length > 0) {
if (qcResult.length < 1) { // Set QC Result
// Set QC Data
useEffect(() => {
if (itemDetail) {
const d = itemDetail;
if (qcRecord.length < 1) { // No QC Data
if (d.status == "pending") { // New QC
if (qcCategory) {
if (qcCategory.qcItems.length > 0) {
const filledQcItems = fillQcResult(qcCategory.qcItems);
setValue("qcResult", filledQcItems);
console.log("%c New QC Record applied:", "color:green", filledQcItems);
}
}
} else {
console.log("%c No QC Record loaded:", "color:green");
//
}
} else { // QC Result fetched
if (qcRecord.some(qc => qc.order !== undefined)) { // If QC Result is filled with order
if (d.status == "escalated") { // Copy the previous QC data for editing

// If no editable Qc Data
if (!qcRecord.some((qc) => !isExist(qc.escalationLogId))) {
const mutableQcData = qcRecord.map(qc => ({ ...qc, escalationLogId: undefined }));
const copiedQcData = [...mutableQcData, ...qcRecord];
setValue("qcResult", copiedQcData);
console.log("%c QC Record copied:", "color:green", copiedQcData);
return;
}
}

// Set QC Result
// const filteredQcResult = qcRecord;
const filteredQcResult = qcRecord.filter((qc) => !isExist(qc.escalationLogId));
console.log("%c QC Result loaded:", "color:green", filteredQcResult);
setQcResult(filteredQcResult);
}
if (qcHistory.length < 1) { // Set QC History
const filteredQcHistory = qcRecord.filter((qc) => isExist(qc.escalationLogId));
setQcHistory(filteredQcHistory);

// Set QC History
if (filteredQcResult.length < qcRecord.length) { // If there are Qc History
if (qcHistory.length < 1) {
const filteredQcHistory = qcRecord.filter((qc) => isExist(qc.escalationLogId));
console.log("%c QC History loaded:", "color:green", filteredQcHistory);
setQcHistory(filteredQcHistory);
}
}
} else {
if (qcCategory) {
const filledQcData = fillQcResult(qcRecord, qcCategory?.qcItems);
console.log("%c QC Result filled:", "color:green", filledQcData);
setValue("qcResult", filledQcData);
}
}
}
}
}, [qcRecord, setValue])
}, [qcRecord, qcCategory, setValue, itemDetail])

const fillQcResult = (qcResults: QcResult[], qcItems: QcData[] = []) => {
let result = [] as QcResult[];
qcResults.forEach((r, index) => {
const target = qcItems.find((t) => t.qcItemId === r.qcItemId);
const n = { ...target, ...r }; //, id: index };
result.push(n);
});
result.sort((a,b) => a.order! - b.order!);
return result;
};

// const [openCollapse, setOpenCollapse] = useState(false)
const [isCollapsed, setIsCollapsed] = useState<boolean>(true);
@@ -435,11 +368,6 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => {
// }, [setValue]);


useEffect(() => {
// console.log("%c QC ItemDetail updated:", "color: gold", itemDetail);
}, [itemDetail]);

const setDefaultQcDecision = (status : string | undefined) => {
const param = status?.toLowerCase();
if (param !== undefined && param !== null) {
@@ -473,37 +401,46 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => {
} else { return 60}
};

// For DEMO
const dummyQcData = useMemo(() => {
const d = itemDetail;
if (d.itemId == 23239 || d.itemNo == "PP2277" || d.itemName == "烚意粉") {
return dummyQcData_E2;
} else {
if (d.jobOrderId === null) {
return dummyQcData_A1;
} else {
return dummyQcData_E1;
}
}
}, [itemDetail])
const formattedDesc = (content: string = "") => {
return (
<>
{content.split("\\n").map((line, index) => (
<span key={index}> {line} <br/></span>
))}
</>
);
}

const dummyQcHeader = useMemo(() => {
const d = itemDetail;
if (d.itemId == 23239 || d.itemNo == "PP2277" || d.itemName == "烚意粉") {
return dummyQcHeader_E2;
} else {
if (d.jobOrderId === null) {
return dummyQcHeader_A1;
} else {
return dummyQcHeader_E1;
}
}
}, [itemDetail])
const QcHeader = useMemo(() => () => {
if (qcCategory === undefined || qcCategory === null) {
return (
<Typography variant="h5" component="h2" sx={{ fontWeight: 'bold', color: '#333' }}>
N/A
</Typography>
);
} else
return (
<>
<Box sx={{ mb: 2, p: 2, backgroundColor: '#f5f5f5', borderRadius: 1 }}>
<Typography variant="h5" component="h2" sx={{ fontWeight: 'bold', color: '#333' }}>
{qcCategory?.name} ({qcCategory?.code})
</Typography>
<Typography variant="subtitle1" sx={{ color: '#666' }}>
<b>品檢類型</b>:{qcType}
</Typography>
<Typography variant="subtitle2" sx={{ color: '#666' }}>
{formattedDesc(qcCategory?.description)}
</Typography>
</Box>
</>
);
}, [qcType, qcCategory]);

return (
<>
<Grid container justifyContent="flex-start" alignItems="flex-start">
{itemDetail ? (
{(qcRecord.length > 0) ? (
// {(qcRecord.length > 0 && qcCategory) ? (
<Grid
container
justifyContent="flex-start"
@@ -518,41 +455,23 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => {
variant="scrollable"
>
<Tab label={t("QC Info")} iconPosition="end" />
{(itemDetail.escResult && itemDetail.escResult?.length > 0) &&
{(escResult && escResult?.length > 0) &&
(<Tab label={t("Escalation History")} iconPosition="end" />)}
</Tabs>
</Grid>
{tabIndex == 0 && (
<>
<Grid item xs={12}>
<Box sx={{ mb: 2, p: 2, backgroundColor: '#f5f5f5', borderRadius: 1 }}>
<Typography variant="h5" component="h2" sx={{ fontWeight: 'bold', color: '#333' }}>
{dummyQcHeader.name}
</Typography>
<Typography variant="subtitle1" sx={{ color: '#666' }}>
<b>品檢類型</b>:{dummyQcHeader.type}
</Typography>
<Typography variant="subtitle2" sx={{ color: '#666' }}>
{dummyQcHeader.description}
</Typography>
</Box>
<QcHeader/>
{/* <QcDataGrid<ModalFormInput, QcData, EntryError>
apiRef={apiRef}
columns={qcColumns}
_formKey="qcResult"
validateRow={validation}
/> */}
<StyledDataGrid
columns={qcColumns}
<QcForm
rows={qcResult}
// rows={qcResult && qcResult.length > 0 ? qcResult : qcItems}
// rows={disabled? qcResult:qcItems}
// autoHeight
sortModel={[]}
// getRowHeight={getRowHeight}
getRowHeight={() => 'auto'}
getRowId={getRowId}
disabled={disabled}
/>
</Grid>
</>
@@ -571,26 +490,12 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => {
</Typography>
</Grid> */}
<Grid item xs={12}>
<EscalationLogTable type="qc" items={itemDetail.escResult || []}/>
<EscalationLogTable type="qc" items={escResult || []}/>
<CollapsibleCard title={t("QC Record")}>
<Box sx={{ mb: 2, p: 2, backgroundColor: '#f5f5f5', borderRadius: 1 }}>
<Typography variant="h5" component="h2" sx={{ fontWeight: 'bold', color: '#333' }}>
{dummyQcHeader.name}
</Typography>
<Typography variant="subtitle1" sx={{ color: '#666' }}>
<b>品檢類型</b>:{dummyQcHeader.type}
</Typography>
<Typography variant="subtitle2" sx={{ color: '#666' }}>
{dummyQcHeader.description}
</Typography>
</Box>
<StyledDataGrid
columns={qcColumns}
<QcHeader/>
<QcForm
disabled={disabled}
rows={qcHistory}
// rows={qcResult && qcResult.length > 0 ? qcResult : qcItems}
// rows={disabled? qcResult:qcItems}
autoHeight
sortModel={[]}
/>
</CollapsibleCard>
</Grid>
@@ -712,7 +617,7 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => {
<FormControlLabel disabled={disabled}
value="3" control={<Radio />}
sx={{"& .Mui-checked": {color: "blue"}}}
label="上報品檢結果" />
label="暫時存放到置物區,並等待品檢結果" />
</>)}
</RadioGroup>
</>
@@ -730,18 +635,6 @@ const QcComponent: React.FC<Props> = ({ itemDetail, disabled = false }) => {
setIsCollapsed={setIsCollapsed}
/>
</Grid>)}
{/* {qcAccept && <Grid item xs={12}>
<Typography variant="h6" display="block" marginBlockEnd={1}>
{t("Escalation Result")}
</Typography>
</Grid>
<Grid item xs={12}>
<EscalationComponent
forSupervisor={true}
isCollapsed={isCollapsed}
setIsCollapsed={setIsCollapsed}
/>
</Grid>} */}
</Grid>
) : <LoadingComponent/>}
</Grid>

+ 245
- 0
src/components/Qc/QcForm.tsx View File

@@ -0,0 +1,245 @@
"use client";

import {
Box, Card, CardContent, Checkbox, Collapse, FormControl,
FormControlLabel, Grid, Radio, RadioGroup, Stack, Tab,
Tabs, TabsProps, TextField, Tooltip, Typography,
} from "@mui/material";
import { useFormContext, Controller, FieldPath } from "react-hook-form";
import { useTranslation } from "react-i18next";
import StyledDataGrid from "../StyledDataGrid";
import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react";
import {
GridColDef,
useGridApiContext,
GridRenderEditCellParams,
useGridApiRef,
} from "@mui/x-data-grid";
import { QcFormInput, QcResult } from "@/app/api/qc";

interface Props {
rows: QcResult[];
disabled?: boolean;
}

const QcForm: React.FC<Props> = ({ rows, disabled = false }) => {
const { t } = useTranslation("purchaseOrder");
const apiRef = useGridApiRef();
const {
register,
formState: { errors, defaultValues, touchedFields },
watch,
control,
setValue,
getValues,
reset,
resetField,
setError,
clearErrors,
} = useFormContext<QcFormInput>();

const qcDisabled = (row : QcResult) => {
return disabled || isExist(row.escalationLogId);
};

const isExist = (data : string | number | undefined) => {
return (data !== null && data !== undefined);
}

function BooleanEditCell(params: GridRenderEditCellParams) {
const apiRef = useGridApiContext();
const { id, field, value } = params;

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
apiRef.current.setEditCellValue({ id, field, value: e.target.checked });
apiRef.current.stopCellEditMode({ id, field }); // commit immediately
};

return <Checkbox checked={!!value} onChange={handleChange} sx={{ p: 0 }} />;
}

const qcColumns: GridColDef[] = useMemo(() => [
{
field: "name",
headerName: t("qcItem"),
wrapText: true,
flex: 2.5,
renderCell: (params) => {
const index = getRowIndex(params);//params.api.getRowIndexRelativeToVisibleRows(params.id);
return (
<Box
sx={{
lineHeight: 1.5,
padding: "4px",
fontSize: 18,
}}
>
<b>{`${params.row.order ?? "N/A"}. ${params.value}`}</b><br/>
{params.row.description}
</Box>
)},
},
{
field: 'qcResult',
headerName: t("qcResult"),
flex: 1,
renderCell: (params) => {
const rowValue = params.row;
const index = getRowIndex(params);//params.api.getRowIndexRelativeToVisibleRows(params.row.id);
// const index = Number(params.id);
// const index = Number(params.row.order - 1);
// console.log(rowValue.row);
return (
<FormControl>
<RadioGroup
row
aria-labelledby="demo-radio-buttons-group-label"
// defaultValue={""}
value={rowValue.qcPassed === undefined ? "" : (rowValue.qcPassed ? "true" : "false")}
onChange={(e) => {
const value = (e.target.value === "true");
// setQcItems((prev) =>
// prev.map((r): QcData => (r.id === params.id ? { ...r, qcPassed: value === "true" } : r))
// );
setValue(`qcResult.${index}.qcPassed`, value);
}}
name={`qcPassed-${params.id}`}
>
<FormControlLabel
value="true"
control={<Radio />}
label="合格"
disabled={qcDisabled(rowValue)}
sx={{
color: rowValue.qcPassed === true ? "green" : "inherit",
"& .Mui-checked": {color: "green"}
}}
/>
<FormControlLabel
value="false"
control={<Radio />}
label="不合格"
disabled={qcDisabled(rowValue)}
sx={{
color: rowValue.qcPassed === false ? "red" : "inherit",
"& .Mui-checked": {color: "red"}
}}
/>
</RadioGroup>
</FormControl>
);
},
},
{
field: "failQty",
headerName: t("failedQty"),
flex: 0.5,
// editable: true,
renderCell: (params) => {
const index = getRowIndex(params);//params.api.getRowIndexRelativeToVisibleRows(params.id);
// const index = Number(params.id);
return (
<TextField
type="number"
value={!params.row.qcPassed? params.value : '0'}
disabled={params.row.qcPassed || qcDisabled(params.row)}
/* TODO improve */
/* Reference: https://grok.com/share/c2hhcmQtNA%3D%3D_10787069-3eec-40af-a7cc-bacbdb86bf05 */
onChange={(e) => {
const v = e.target.value;
const next = v === '' ? undefined : Number(v);
if (Number.isNaN(next)) return;
setValue(`qcResult.${index}.failQty`, next);
}}
// onBlur={(e) => {
// const v = e.target.value;
// const next = v === '' ? undefined : Number(v);
// if (Number.isNaN(next)) return;
// setValue(`qcResult.${index}.failQty`, next);
// }}
onClick={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
inputProps={{ min: 0 }}
sx={{ width: '100%',
"& .MuiInputBase-input": {
padding: "0.75rem",
fontSize: 24,
},
}}
/>
);
},
},
{
field: "remarks",
headerName: t("remarks"),
flex: 2,
renderCell: (params) => {
// const index = Number(params.id);//params.api.getRowIndexRelativeToVisibleRows(params.id);
const index = getRowIndex(params);//params.api.getRowIndexRelativeToVisibleRows(params.id);
return (
<TextField
size="small"
defaultValue={params.value}
disabled={qcDisabled(params.row)}
onBlur={(e) => {
const value = e.target.value;
setValue(`qcResult.${index}.remarks`, value);
}}
// onChange={(e) => {
// const remarks = e.target.value;
// // const next = v === '' ? undefined : Number(v);
// // if (Number.isNaN(next)) return;
// // setQcItems((prev) =>
// // prev.map((r) => (r.id === params.id ? { ...r, remarks: remarks } : r))
// // );
// }}
// {...register(`qcResult.${index}.remarks`, {
// required: "remarks required!",
// })}
onClick={(e) => e.stopPropagation()}
onMouseDown={(e) => e.stopPropagation()}
onKeyDown={(e) => e.stopPropagation()}
sx={{ width: '100%',
"& .MuiInputBase-input": {
padding: "0.75rem",
fontSize: 24,
},
}}
/>
);
},
},
], [])

// const getRowId = (row :any) => {
// return qcRecord.findIndex(qc => qc == row);
// // return row.id || `${row.name}-${Math.random().toString(36).substr(2, 9)}`;
// };

const getRowHeight = (row :any) => { // Not used?
console.log("row", row);
if (!row.model.name) {
return (row.model.name.length ?? 10) * 1.2 + 30;
} else { return 60}
};

const getRowIndex = (params: any) => {
return params.api.getRowIndexRelativeToVisibleRows(params.id);
// return params.row.id;
}

return (
<>
<StyledDataGrid
columns={qcColumns}
rows={rows}
// autoHeight
sortModel={[]}
getRowHeight={() => 'auto'}
/>
</>
);
};
export default QcForm;

+ 669
- 0
src/components/Qc/QcStockInModal.tsx View File

@@ -0,0 +1,669 @@
"use client";
import { QcItemWithChecks, QcData } from "@/app/api/qc";
import {
Autocomplete,
Box,
Button,
Divider,
Grid,
Modal,
ModalProps,
Stack,
Tab,
Tabs,
TabsProps,
TextField,
Typography,
} from "@mui/material";
import { Dispatch, SetStateAction, useCallback, useEffect, useMemo, useState } from "react";
import { FormProvider, SubmitErrorHandler, SubmitHandler, useForm } from "react-hook-form";
import { StockInLineRow } from "../PoDetail/PoInputGrid";
import { useTranslation } from "react-i18next";
import StockInForm from "../StockIn/StockInForm";
import QcComponent from "./QcComponent";
import PutAwayForm from "../PoDetail/PutAwayForm";
import { GridRowModes, GridRowSelectionModel, useGridApiRef } from "@mui/x-data-grid";
import {msg, submitDialogWithWarning} from "../Swal/CustomAlerts";
import { INPUT_DATE_FORMAT, arrayToDateString, dayjsToDateTimeString } from "@/app/utils/formatUtil";
import dayjs from "dayjs";
import { fetchPoQrcode } from "@/app/api/pdf/actions";
import { downloadFile } from "@/app/utils/commonUtil";
import { PrinterCombo } from "@/app/api/settings/printer";
import { EscalationResult } from "@/app/api/escalation";
import { SessionWithTokens } from "@/config/authConfig";
import { GridRowModesModel } from "@mui/x-data-grid";
import { isEmpty } from "lodash";
import { EscalationCombo } from "@/app/api/user";
import { truncateSync } from "fs";
import { ModalFormInput, StockInLineInput, StockInLine } from "@/app/api/stockIn";
import { StockInLineEntry, updateStockInLine, printQrCodeForSil, PrintQrCodeForSilRequest } from "@/app/api/stockIn/actions";
import { fetchStockInLineInfo } from "@/app/api/stockIn/actions";
import FgStockInForm from "../StockIn/FgStockInForm";
import LoadingComponent from "../General/LoadingComponent";


const style = {
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
bgcolor: "background.paper",
// pt: 5,
// px: 5,
// pb: 10,
display: "block",
width: { xs: "90%", sm: "90%", md: "90%" },
height: { xs: "90%", sm: "90%", md: "90%" },
};
interface CommonProps extends Omit<ModalProps, "children"> {
// itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] } | undefined;
inputDetail: StockInLineInput | undefined;
session: SessionWithTokens | null;
warehouse?: any[];
printerCombo: PrinterCombo[];
onClose: () => void;
skipQc?: Boolean;
}
interface Props extends CommonProps {
// itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] };
}
const QcStockInModal: React.FC<Props> = ({
open,
onClose,
// itemDetail,
inputDetail,
session,
warehouse,
printerCombo,
skipQc = false,
}) => {
const {
t,
i18n: { language },
} = useTranslation("purchaseOrder");

const [stockInLineInfo, setStockInLineInfo] = useState<StockInLine>();
const [isLoading, setIsLoading] = useState<Boolean>(false);
// const [skipQc, setSkipQc] = useState<Boolean>(false);
// const [viewOnly, setViewOnly] = useState(false);

// Select Printer
const [selectedPrinter, setSelectedPrinter] = useState(printerCombo[0]);
const [printQty, setPrintQty] = useState(1);
const [tabIndex, setTabIndex] = useState(0);

const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
(_e, newValue) => {
setTabIndex(newValue);
},
[],
);

const fetchStockInLineData = useCallback(
async (stockInLineId: number) => {
try {
const res = await fetchStockInLineInfo(stockInLineId);
if (res) {
console.log("%c Fetched Stock In Line: ", "color:orange", res);
setStockInLineInfo({...inputDetail, ...res, expiryDate: inputDetail?.expiryDate}); // TODO review to overwrite res with inputDetail instead (revise PO fetching data)
// fetchQcResultData(stockInLineId);
} else throw("Result is undefined");
} catch (e) {
console.log("%c Error when fetching Stock In Line: ", "color:red", e);
alert("Something went wrong, please retry");
closeHandler({}, "backdropClick");
}
},[fetchStockInLineInfo, inputDetail]
);

// Fetch info if id is input
useEffect(() => {
setIsLoading(true);
if (inputDetail && open) {
console.log("%c Opened Modal with input:", "color:yellow", inputDetail);
if (inputDetail.id) {
const id = inputDetail.id;
fetchStockInLineData(id);
}
}
}, [open]);

// Make sure stock in line info is fetched
useEffect(() => {
if (stockInLineInfo) {
if (stockInLineInfo.id) {
if (isLoading) {
formProps.reset({
...defaultNewValue
});
console.log("%c Modal loaded successfully", "color:lime");
setIsLoading(false);
}
}
}
}, [stockInLineInfo]);

const defaultNewValue = useMemo(() => {
const d = stockInLineInfo;
if (d !== undefined) {
// console.log("%c sil info", "color:yellow", d )
return (
{
...d,
// status: d.status ?? "pending",
productionDate: d.productionDate ? arrayToDateString(d.productionDate, "input") : undefined,
expiryDate: d.expiryDate ? arrayToDateString(d.expiryDate, "input") : undefined,
receiptDate: d.receiptDate ? arrayToDateString(d.receiptDate, "input")
: dayjs().add(0, "month").format(INPUT_DATE_FORMAT),
acceptQty: d.demandQty?? d.acceptedQty,
// escResult: (d.escResult && d.escResult?.length > 0) ? d.escResult : [],
// qcResult: (d.qcResult && d.qcResult?.length > 0) ? d.qcResult : [],//[...dummyQCData],
warehouseId: d.defaultWarehouseId ?? 1,
putAwayLines: d.putAwayLines?.map((line) => ({...line, printQty: 1, _isNew: false, _disableDelete: true})) ?? [],
} as ModalFormInput
)
} return undefined
}, [stockInLineInfo])

// const [qcItems, setQcItems] = useState(dummyQCData)
const formProps = useForm<ModalFormInput>({
defaultValues: {
...defaultNewValue,
},
});
const closeHandler = useCallback<NonNullable<ModalProps["onClose"]>>(
() => {
setStockInLineInfo(undefined);
formProps.reset({});
onClose?.();
},
[onClose],
);
const isPutaway = () => {
if (stockInLineInfo) {
const status = stockInLineInfo.status;
return status == "received";

} else return false;
};

// Get show putaway
const showPutaway = useMemo(() => {
if (stockInLineInfo) {
const status = stockInLineInfo.status;
return status !== "pending" && status !== "escalated" && status !== "rejected";
}
return false;
}, [stockInLineInfo]);

// Get is view only
const viewOnly = useMemo(() => {
if (stockInLineInfo) {
if (stockInLineInfo.status) {
const status = stockInLineInfo.status;
const isViewOnly = status.toLowerCase() == "completed"
|| status.toLowerCase() == "partially_completed" // TODO update DB
|| status.toLowerCase() == "rejected"
|| (status.toLowerCase() == "escalated" && session?.id != stockInLineInfo.handlerId)
if (showPutaway) { setTabIndex(1); } else { setTabIndex(0); }
return isViewOnly;
}
}
return true;
}, [stockInLineInfo])

const [openPutaway, setOpenPutaway] = useState(false);
const onOpenPutaway = useCallback(() => {
setOpenPutaway(true);
}, []);
const onClosePutaway = useCallback(() => {
setOpenPutaway(false);
}, []);

// Stock In submission handler
const onSubmitStockIn = useCallback<SubmitHandler<ModalFormInput>>(
async (data, event) => {
console.log("Stock In Submission:", event!.nativeEvent);
// Extract only stock-in related fields
const stockInData = {
// quantity: data.quantity,
// receiptDate: data.receiptDate,
// batchNumber: data.batchNumber,
// expiryDate: data.expiryDate,
// warehouseId: data.warehouseId,
// location: data.location,
// unitCost: data.unitCost,
data: data,
// Add other stock-in specific fields from your form
};
console.log("Stock In Data:", stockInData);
// Handle stock-in submission logic here
// e.g., call API, update state, etc.
},
[],
);

// QC submission handler
const onSubmitErrorQc = useCallback<SubmitErrorHandler<ModalFormInput>>(
async (data, event) => {
console.log("Error", data);
}, []
);

// QC submission handler
const onSubmitQc = useCallback<SubmitHandler<ModalFormInput>>(
async (data, event) => {
console.log("QC Submission:", event!.nativeEvent);
console.log("Validating:", data.qcResult);
// TODO: Move validation into QC page

// if (errors.length > 0) {
// alert(`未完成品檢: ${errors.map((err) => err[1].message)}`);
// return;
// }

// Get QC data from the shared form context
const qcAccept = data.qcDecision == 1;
// const qcAccept = data.qcAccept;
let acceptQty = Number(data.acceptQty);
const qcResults = data.qcResult?.filter((qc) => qc.escalationLogId === undefined) || []; // Remove old QC data
// const qcResults = data.qcResult as PurchaseQcResult[]; // qcItems;
// const qcResults = viewOnly? data.qcResult as PurchaseQcResult[] : qcItems;
// Validate QC data
const validationErrors : string[] = [];

// Check if failed items have failed quantity
const failedItemsWithoutQty = qcResults.filter(item =>
item.qcPassed === false && (!item.failQty || item.failQty <= 0)
);
if (failedItemsWithoutQty.length > 0) {
validationErrors.push(`${t("Failed items must have failed quantity")}`);
// validationErrors.push(`${t("Failed items must have failed quantity")}: ${failedItemsWithoutQty.map(item => item.code).join(', ')}`);
}

// Check if QC accept decision is made
if (data.qcDecision === undefined) {
// if (qcAccept === undefined) {
validationErrors.push(t("QC decision is required"));
}

// Check if accept quantity is valid
if (data.qcDecision == 2) {
acceptQty = 0;
} else {
if (acceptQty === undefined || acceptQty <= 0) {
validationErrors.push("Accept quantity must be greater than 0");
}
}
// Check if dates are input
// if (data.productionDate === undefined || data.productionDate == null) {
// alert("請輸入生產日期!");
// return;
// }
if (data.expiryDate === undefined || data.expiryDate == null) {
alert("請輸入到期日!");
return;
}
if (!qcResults.every((qc) => qc.qcPassed) && qcAccept && stockInLineInfo?.status != "escalated") { //TODO: fix it please!
validationErrors.push("有不合格檢查項目,無法收貨!");
// submitDialogWithWarning(() => postStockInLineWithQc(qcData), t, {title:"有不合格檢查項目,確認接受收貨?",
// confirmButtonText: t("confirm putaway"), html: ""});
// return;
}

// Check if all QC items have results
const itemsWithoutResult = qcResults.filter(item => item.qcPassed === undefined);
if (itemsWithoutResult.length > 0 && stockInLineInfo?.status != "escalated") { //TODO: fix it please!
validationErrors.push(`${t("QC items without result")}`);
// validationErrors.push(`${t("QC items without result")}: ${itemsWithoutResult.map(item => item.code).join(', ')}`);
}

if (validationErrors.length > 0 && !skipQc) {
console.error("QC Validation failed:", validationErrors);
alert(`未完成品檢: ${validationErrors}`);
return;
}

const qcData = {
dnNo : data.dnNo? data.dnNo : "DN00000",
// dnDate : data.dnDate? arrayToDateString(data.dnDate, "input") : dayjsToInputDateString(dayjs()),
productionDate : arrayToDateString(data.productionDate, "input"),
expiryDate : arrayToDateString(data.expiryDate, "input"),
receiptDate : arrayToDateString(data.receiptDate, "input"),
qcAccept: qcAccept? qcAccept : false,
acceptQty: acceptQty? acceptQty : 0,
// qcResult: itemDetail.status != "escalated" ? qcResults.map(item => ({
qcResult: qcResults.map(item => ({
// id: item.id,
qcItemId: item.qcItemId,
// code: item.code,
// qcDescription: item.qcDescription,
qcPassed: item.qcPassed? item.qcPassed : false,
failQty: (item.failQty && !item.qcPassed) ? item.failQty : 0,
// failedQty: (typeof item.failedQty === "number" && !item.isPassed) ? item.failedQty : 0,
remarks: item.remarks || '',
})),
};
// const qcData = data;

console.log("QC Data for submission:", qcData);
if (data.qcDecision == 3) { // Escalate
if (data.escalationLog?.handlerId == undefined) { alert("請選擇上報負責同事!"); return; }
else if (data.escalationLog?.handlerId < 1) { alert("上報負責同事資料有誤"); return; }
const escalationLog = {
type : "qc",
status : "pending", // TODO: update with supervisor decision
reason : data.escalationLog?.reason,
recordDate : dayjsToDateTimeString(dayjs()),
handlerId : data.escalationLog?.handlerId,
}
console.log("Escalation Data for submission", escalationLog);
await postStockInLine({...qcData, escalationLog});

} else {
await postStockInLine(qcData);
}

if (qcData.qcAccept) {
// submitDialogWithWarning(onOpenPutaway, t, {title:"Save success, confirm to proceed?",
// confirmButtonText: t("confirm putaway"), html: ""});
// onOpenPutaway();
closeHandler({}, "backdropClick");
// setTabIndex(1); // Need to go Putaway tab?
} else {
closeHandler({}, "backdropClick");
}
msg("已更新來貨狀態");
return ;

},
[onOpenPutaway, formProps.formState.errors],
);

const postStockInLine = useCallback(async (args: ModalFormInput) => {
const submitData = {
...stockInLineInfo, ...args
} as StockInLineEntry & ModalFormInput;
console.log("Submitting", submitData);

const res = await updateStockInLine(submitData);
return res;
}, [stockInLineInfo])

// Put away model
const [pafRowModesModel, setPafRowModesModel] = useState<GridRowModesModel>({})
const [pafRowSelectionModel, setPafRowSelectionModel] = useState<GridRowSelectionModel>([])
const pafSubmitDisable = useMemo(() => {
return Object.entries(pafRowModesModel).length > 0 || Object.entries(pafRowModesModel).some(([key, value], index) => value.mode === GridRowModes.Edit)
}, [pafRowModesModel])
// Putaway submission handler
const onSubmitPutaway = useCallback<SubmitHandler<ModalFormInput>>(
async (data, event) => {
// Extract only putaway related fields
const putawayData = {
acceptQty: Number(data.acceptQty?? (stockInLineInfo?.demandQty?? (stockInLineInfo?.acceptedQty))), //TODO improve
warehouseId: data.warehouseId,
status: data.status, //TODO Fix it!
// ...data,
// dnDate : data.dnDate? arrayToDateString(data.dnDate, "input") : dayjsToInputDateString(dayjs()),
productionDate : arrayToDateString(data.productionDate, "input"),
expiryDate : arrayToDateString(data.expiryDate, "input"),
receiptDate : arrayToDateString(data.receiptDate, "input"),

// for putaway data
inventoryLotLines: data.putAwayLines?.filter((line) => line._isNew !== false)

// Add other putaway specific fields
} as ModalFormInput;
console.log("Putaway Data:", putawayData);

console.log("DEBUG",data.putAwayLines);
// if (data.putAwayLines!!.filter((line) => line._isNew !== false).length <= 0) {
// alert("請新增上架資料!");
// return;
// }
if (data.putAwayLines!!.filter((line) => /[^0-9]/.test(String(line.qty))).length > 0) { //TODO Improve
alert("上架數量不正確!");
return;
}
if (data.putAwayLines!!.reduce((acc, cur) => acc + Number(cur.qty), 0) > putawayData.acceptQty!!) {
alert(`上架數量不能大於 ${putawayData.acceptQty}!`);
return;
}
// Handle putaway submission logic here
const res = await postStockInLine(putawayData);
console.log("result ", res);
// Close modal after successful putaway
closeHandler({}, "backdropClick");
},
[closeHandler],
);
// Print handler
const [isPrinting, setIsPrinting] = useState(false)
const handlePrint = useCallback(async () => {
// console.log("Print putaway documents");
console.log("%c data", "background: white; color: red", formProps.watch("putAwayLines"));
// Handle print logic here
// window.print();
// const postData = { stockInLineIds: [itemDetail.id]};
// const response = await fetchPoQrcode(postData);
// if (response) {
// downloadFile(new Uint8Array(response.blobValue), response.filename)
// }
try {
setIsPrinting(() => true)
if ((formProps.watch("putAwayLines") ?? []).filter((line) => /[^0-9]/.test(String(line.printQty))).length > 0) { //TODO Improve
alert("列印數量不正確!");
return;
}
// console.log(pafRowSelectionModel)
const printList = formProps.watch("putAwayLines")?.filter((line) => ((pafRowSelectionModel ?? []).some((model) => model === line.id))) ?? []
// const printQty = printList.reduce((acc, cur) => acc + cur.printQty, 0)
// console.log(printQty)
const data: PrintQrCodeForSilRequest = {
stockInLineId: stockInLineInfo?.id ?? 0,
printerId: selectedPrinter.id,
printQty: printQty
}
const response = await printQrCodeForSil(data);
if (response) {
console.log(response)
}
} finally {
setIsPrinting(() => false)
}
// }, [pafRowSelectionModel, printQty, selectedPrinter]);
}, [stockInLineInfo?.id, pafRowSelectionModel, printQty, selectedPrinter]);
const acceptQty = formProps.watch("acceptedQty")

// const checkQcIsPassed = useCallback((qcItems: PurchaseQcResult[]) => {
// const isPassed = qcItems.every((qc) => qc.qcPassed);
// console.log(isPassed)
// if (isPassed) {
// formProps.setValue("passingQty", acceptQty)
// } else {
// formProps.setValue("passingQty", 0)
// }
// return isPassed
// }, [acceptQty, formProps])

const printQrcode = useCallback(
async () => {
setIsPrinting(true);
try {
const postData = { stockInLineIds: [stockInLineInfo?.id] };
const response = await fetchPoQrcode(postData);
if (response) {
console.log(response);
downloadFile(new Uint8Array(response.blobValue), response.filename!);
}
} catch (e) {
console.log("%c Error downloading QR Code", "color:red", e);
} finally {
setIsPrinting(false);
}
},
[stockInLineInfo],
);

return (
<>
<FormProvider {...formProps}>
<Modal open={open} onClose={closeHandler}>
<Box
sx={{
...style,
// padding: 2,
maxHeight: "90vh",
overflowY: "auto",
marginLeft: 3,
marginRight: 3,
// overflow: "hidden",
display: 'flex',
flexDirection: 'column',
}}
>
{(!isLoading && stockInLineInfo) ? (<>
<Box sx={{ position: 'sticky', top: 0, bgcolor: 'background.paper',
zIndex: 5, borderBottom: 2, borderColor: 'divider', width: "100%"}}>
<Tabs
value={tabIndex}
onChange={handleTabChange}
variant="scrollable"
sx={{pl: 2, pr: 2, pt: 2}}
>
<Tab label={
showPutaway ? t("dn and qc info") : t("qc processing")
} iconPosition="end" />
{showPutaway && <Tab label={t("putaway processing")} iconPosition="end" />}
</Tabs>
</Box>
<Grid
container
justifyContent="flex-start"
alignItems="flex-start"
sx={{padding: 2}}
>
<Grid item xs={12}>
{tabIndex === 0 &&
<Box>
<Grid item xs={12}>
<Typography variant="h6" display="block" marginBlockEnd={1}>
{t("Delivery Detail")}
</Typography>
</Grid>
{stockInLineInfo.jobOrderId ? (
<FgStockInForm itemDetail={stockInLineInfo} disabled={viewOnly || showPutaway} />
) : (
<StockInForm itemDetail={stockInLineInfo} disabled={viewOnly || showPutaway} />
)
}
{skipQc === false && (
<QcComponent
itemDetail={stockInLineInfo}
disabled={viewOnly || showPutaway}
/>)
}
<Stack direction="row" justifyContent="flex-end" gap={1} sx={{pt:2}}>
{(!viewOnly && !showPutaway) && (<Button
id="Submit"
type="button"
variant="contained"
color="primary"
sx={{ mt: 1 }}
onClick={formProps.handleSubmit(onSubmitQc, onSubmitErrorQc)}
>
{skipQc ? t("confirm") : t("confirm qc result")}
</Button>)}
</Stack>
</Box>
}

{tabIndex === 1 &&
<Box>
<PutAwayForm
itemDetail={stockInLineInfo}
warehouse={warehouse!}
disabled={viewOnly}
setRowModesModel={setPafRowModesModel}
setRowSelectionModel={setPafRowSelectionModel}
/>
</Box>
}
</Grid>
</Grid>
{tabIndex == 1 && (
<Stack direction="row" justifyContent="flex-end" gap={1} sx={{m:3, mt:"auto"}}>
<Autocomplete
disableClearable
options={printerCombo}
defaultValue={selectedPrinter}
onChange={(event, value) => {
setSelectedPrinter(value)
}}
renderInput={(params) => (
<TextField
{...params}
variant="outlined"
label={t("Printer")}
sx={{ width: 300}}
/>
)}
/>
<TextField
variant="outlined"
label={t("Print Qty")}
defaultValue={printQty}
onChange={(event) => {
event.target.value = event.target.value.replace(/[^0-9]/g, '')

setPrintQty(Number(event.target.value))
}}
sx={{ width: 300}}
/>
<Button
id="printButton"
type="button"
variant="contained"
color="primary"
sx={{ mt: 1 }}
onClick={handlePrint}
disabled={isPrinting || printerCombo.length <= 0 || pafSubmitDisable}
>
{isPrinting ? t("Printing") : t("print")}
</Button>
<Button
id="demoPrint"
type="button"
variant="contained"
color="primary"
sx={{ mt: 1 }}
onClick={printQrcode}
disabled={isPrinting}
>
{isPrinting ? t("downloading") : t("download Qr Code")}
</Button>
</Stack>
)}
</>) : <LoadingComponent/>}
</Box>
</Modal>
</FormProvider>
</>
);
};
export default QcStockInModal;

src/components/PoDetail/dummyQcTemplate.tsx → src/components/Qc/dummyQcTemplate.tsx View File


Loading…
Cancel
Save