"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, GridRowIdGetter, GridRowModel, useGridApiContext, GridRenderCellParams, GridRenderEditCellParams, useGridApiRef, GridRowSelectionModel, } from "@mui/x-data-grid"; import InputDataGrid from "../InputDataGrid"; import { TableRow } from "../InputDataGrid/InputDataGrid"; 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 { 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 "../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, 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: QcInput; // itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] } & { escResult?: EscalationResult[] }; // qc: QcItemWithChecks[]; disabled: boolean; // qcItems: QcData[] // setQcItems: Dispatch> } type EntryError = | { [field in keyof QcData]?: string; } | undefined; type QcRow = TableRow, EntryError>; // fetchQcItemCheck const QcComponent: React.FC = ({ itemDetail, disabled = false }) => { const { t } = useTranslation("purchaseOrder"); const apiRef = useGridApiRef(); const { register, formState: { errors, defaultValues, touchedFields }, watch, control, setValue, getValues, reset, resetField, setError, clearErrors, } = useFormContext(); const [tabIndex, setTabIndex] = useState(0); const [rowSelectionModel, setRowSelectionModel] = useState(); const [escalationHistory, setEscalationHistory] = useState(dummyEscalationHistory); const qcAccept = watch("qcAccept"); const qcDecision = watch("qcDecision"); //WIP // const qcResult = useMemo(() => [...watch("qcResult")], [watch("qcResult")]); const [qcCategory, setQcCategory] = useState(); const qcRecord = useMemo(() => { // Need testing const value = watch('qcResult'); //console.log("%c QC update!", "color:green", value); return Array.isArray(value) ? [...value] : []; }, [watch('qcResult')]); const [qcHistory, setQcHistory] = useState([]); const [qcResult, setQcResult] = useState([]); const [escResult, setEscResult] = useState([]); // const [qcAccept, setQcAccept] = useState(true); // const [qcItems, setQcItems] = useState(dummyQCData) const qcDisabled = (row : QcResult) => { return disabled || isExist(row.escalationLogId); }; const isExist = (data : string | number | undefined) => { return (data !== null && data !== undefined); } const handleTabChange = useCallback>( (_e, newValue) => { setTabIndex(newValue); }, [], ); 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, condition: boolean, message: string) : boolean => { // console.log("Checking if " + message) if (condition) { setError(field, { message: message}); return false; } else { clearErrors(field); return true; } } //// validate form const accQty = watch("acceptQty"); const validateForm = useCallback(() => { if (qcDecision == 1) { if (isNaN(accQty) || accQty === undefined || accQty === null || typeof(accQty) != "number") { setError("acceptQty", { message: t("value must be a number") }); } else if (!Number.isInteger(accQty)) { setError("acceptQty", { message: t("value must be integer") }); } if (accQty > itemDetail.acceptedQty) { setError("acceptQty", { message: `${t("acceptQty must not greater than")} ${ itemDetail.acceptedQty}` }); } else if (accQty < 1) { setError("acceptQty", { message: t("minimal value is 1") }); } else console.log("%c Validated accQty:", "color:yellow", accQty); } },[setError, qcDecision, accQty, itemDetail]) useEffect(() => { // W I P // ----- if (qcDecision == 1) { if (validateFieldFail("acceptQty", accQty > itemDetail.acceptedQty, `${t("acceptQty must not greater than")} ${ itemDetail.acceptedQty}`)) return; if (validateFieldFail("acceptQty", accQty < 1, t("minimal value is 1"))) return; if (validateFieldFail("acceptQty", isNaN(accQty), t("value must be a number"))) return; } const qcResultItems = qcResult; //console.log("Validating:", qcResultItems); // Check if failed items have failed quantity const failedItemsWithoutQty = qcResultItems.filter(item => item.qcPassed === false && (!item.failQty || item.failQty <= 0) ); if (validateFieldFail("qcResult", failedItemsWithoutQty.length > 0, `${t("Failed items must have failed quantity")}`)) return; // Check if all QC items have results const itemsWithoutResult = qcResultItems.filter(item => item.qcPassed === undefined); if (validateFieldFail("qcDecision", (itemsWithoutResult.length > 0 && itemDetail.status != "escalated"), `${t("QC items without result")}`)) return; if (validateFieldFail("qcDecision", (!qcResultItems.every((qc) => qc.qcPassed) && qcDecision == 1 && itemDetail.status != "escalated"), "有不合格檢查項目,無法收貨!")) return; // TODO: Fix it please // submitDialogWithWarning(() => postStockInLineWithQc(qcData), t, {title:"有不合格檢查項目,確認接受收貨?", // confirmButtonText: t("confirm putaway"), html: ""}); // return; // console.log("Validated without errors"); }, [accQty, qcDecision, watch("qcResult")]); useEffect(() => { clearErrors(); validateForm(); }, [clearErrors, validateForm]); /// validate datagrid const validation = useCallback( (newRow: GridRowModel): EntryError => { const error: EntryError = {}; // const { qcItemId, failQty } = newRow; return Object.keys(error).length > 0 ? error : undefined; }, [], ); // Set initial value for acceptQty useEffect(() => { if (itemDetail?.demandQty > 0) { //!== undefined) { 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 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] ); // 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); // 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, 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(true); const onFailedOpenCollapse = useCallback((qcItems: QcResult[]) => { const isFailed = qcItems.some((qc) => !qc.qcPassed) // console.log(isFailed) if (isFailed) { setIsCollapsed(true) } else { setIsCollapsed(false) } }, []) // const handleRadioChange = useCallback((event: React.ChangeEvent) => { // const value = event.target.value === 'true'; // setValue("qcAccept", value); // }, [setValue]); const setDefaultQcDecision = (status : string | undefined) => { const param = status?.toLowerCase(); if (param !== undefined && param !== null) { if (param == "received" || param == "completed" || param == "partially_completed") { return 1; } else if (param == "rejected") { return 2; } else if (param == "escalated") { return 1; // For new flow // return 3; } else { return undefined; } } else { return undefined; } } // }, [watch("qcResult")]); // useEffect(() => { // // onFailedOpenCollapse(qcItems) // }, [qcItems]); 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 formattedDesc = (content: string = "") => { return ( <> {content.split("\\n").map((line, index) => ( {line}
))} ); } const QcHeader = useMemo(() => () => { if (qcCategory === undefined || qcCategory === null) { return ( N/A ); } else return ( <> {qcCategory?.name} ({qcCategory?.code}) 品檢類型:{qcType} {formattedDesc(qcCategory?.description)} ); }, [qcType, qcCategory]); return ( <> {(qcRecord.length > 0) ? ( // {(qcRecord.length > 0 && qcCategory) ? ( {(escResult && escResult?.length > 0) && ()} {tabIndex == 0 && ( <> {/* apiRef={apiRef} columns={qcColumns} _formKey="qcResult" validateRow={validation} /> */} )} {tabIndex == 1 && ( <> {/* */} {/* {t("Escalation Info")} */} )} {t("Qc Decision")} ( <> {/* {errors.qcDecision?.message} */} { const value = e.target.value.toString();// === 'true'; const input = document.getElementById('accQty') as HTMLInputElement; //TODO improve // console.log("%c AccQty Error", "color:red", errors.acceptQty); if (input) { // Selected Reject in new flow with Error if (value == "1") { // Selected Accept input.value = Number(accQty).toString(); } else { if (Boolean(errors.acceptQty)) { setValue("acceptQty", 0); } input.value = '0'; } } // setValue("acceptQty", itemDetail.acceptedQty ?? 0); // clearErrors("acceptQty"); // } field.onChange(value); }} > } label="接受來貨" /> {(detailMode || (disabled && accQty != itemDetail.acceptedQty && qcDecision == 1)) && ( //TODO Improve { const value = e.target.value; const input = document.getElementById('accQty') as HTMLInputElement; input.value = Number(value).toString() setValue(`acceptQty`, Number(value)); }} onInput={(e: React.ChangeEvent) => { const input = e.target.value; const numReg = /^[0-9]+$/ let r = ''; if (!numReg.test(input)) { const result = input.replace(/\D/g, ""); r = (result === '' ? result : Number(result)).toString(); } else { r = Number(input).toString() } e.target.value = r; }} inputProps={{ min: 1, max:itemDetail.acceptedQty }} // onChange={(e) => { // const inputValue = e.target.value; // if (inputValue === '' || /^[0-9]*$/.test(inputValue)) { // setValue("acceptQty", Number(inputValue === '' ? null : parseInt(inputValue, 10))); // } // }} // {...register("acceptQty", { // required: "acceptQty required!", // })} error={Boolean(errors.acceptQty)} helperText={errors.acceptQty?.message} /> )} } sx={{"& .Mui-checked": {color: "red"}}} label= {detailMode ? "全部拒絕並退貨" : "不接受並需要退貨"} /> {(itemDetail.status == "pending" || disabled) && (<> } sx={{"& .Mui-checked": {color: "blue"}}} label="暫時存放到置物區,並等待品檢結果" /> )} )} /> {qcDecision == 3 && ( // {!qcAccept && ( )} ) : } ); }; export default QcComponent;