From 5e4336e1ebef1e4a566357e32228abf7a2d5cbff Mon Sep 17 00:00:00 2001 From: kelvinsuen Date: Thu, 21 Aug 2025 13:57:13 +0800 Subject: [PATCH] update po --- src/app/api/po/actions.ts | 7 +- src/app/api/qc/index.ts | 10 + src/components/General/LoadingComponent.tsx | 19 ++ .../InputDataGrid/InputDataGrid.tsx | 1 + .../PoDetail/EscalationComponent.tsx | 63 ++-- src/components/PoDetail/PoDetail.tsx | 86 +++--- src/components/PoDetail/PoInfoCard.tsx | 2 +- src/components/PoDetail/QcFormVer2.tsx | 74 ++--- .../PoDetail/QcStockInModalVer2.tsx | 40 ++- src/components/PoDetail/StockInFormVer2.tsx | 10 +- src/components/PoDetail/dummyQcTemplate.tsx | 44 +-- src/i18n/zh/purchaseOrder.json | 280 +++++++++--------- 12 files changed, 353 insertions(+), 283 deletions(-) create mode 100644 src/components/General/LoadingComponent.tsx diff --git a/src/app/api/po/actions.ts b/src/app/api/po/actions.ts index bd6e972..92f1b7f 100644 --- a/src/app/api/po/actions.ts +++ b/src/app/api/po/actions.ts @@ -33,11 +33,12 @@ export interface StockInLineEntry { expiryDate?: string; } -export interface PurchaseQcResult { +export interface PurchaseQcResult{ qcItemId: number; - isPassed: boolean, + isPassed: boolean; failQty: number; - remarks?: string + remarks?: string; + } export interface StockInInput { status: string; diff --git a/src/app/api/qc/index.ts b/src/app/api/qc/index.ts index 26094c6..6c65043 100644 --- a/src/app/api/qc/index.ts +++ b/src/app/api/qc/index.ts @@ -15,6 +15,16 @@ export interface QcItemWithChecks { description: string | undefined; } +export interface QcData { + id: number, + code: string, + name: string, + qcDescription: string, + isPassed: boolean | undefined + failQty: number | undefined + remarks: string | undefined +} + export const fetchQcItemCheckList = cache(async () => { return serverFetchJson(`${BASE_API_URL}/qc/list`, { next: { tags: ["qc"] }, diff --git a/src/components/General/LoadingComponent.tsx b/src/components/General/LoadingComponent.tsx new file mode 100644 index 0000000..fc802b2 --- /dev/null +++ b/src/components/General/LoadingComponent.tsx @@ -0,0 +1,19 @@ +import {Box, CircularProgress, Grid} from "@mui/material"; + +export const LoadingComponent: React.FC = () => { + return ( + <> + + + + + + + ) +} +export default LoadingComponent; diff --git a/src/components/InputDataGrid/InputDataGrid.tsx b/src/components/InputDataGrid/InputDataGrid.tsx index 210fb6a..b4e9062 100644 --- a/src/components/InputDataGrid/InputDataGrid.tsx +++ b/src/components/InputDataGrid/InputDataGrid.tsx @@ -84,6 +84,7 @@ export interface SelectionInputDataGridProps { columns: GridColDef[]; validateRow: (newRow: GridRowModel>) => E; needAdd?: boolean; + showRemoveBtn?: boolean; } export type Props = diff --git a/src/components/PoDetail/EscalationComponent.tsx b/src/components/PoDetail/EscalationComponent.tsx index 12e0b81..5337eab 100644 --- a/src/components/PoDetail/EscalationComponent.tsx +++ b/src/components/PoDetail/EscalationComponent.tsx @@ -83,10 +83,18 @@ const EscalationComponent: React.FC = ({ return ( // <> - + {/* */} - + 上報結果 + {/* {isCollapsed ? ( + + ) : ( + + )} */} + + {/* = ({ )} } - /> + /> */} - {forSupervisor ? ( - - - } label="合格" /> - } label="不合格" /> - - - ): undefined} - + + } label="合格" /> + } label="不合格" /> + + + ): undefined} + {forSupervisor && ()} + {/* = ({ onChange={handleInputChange} InputProps={{ inputProps: { min: 1 } }} placeholder="請輸入數量" - /> + /> */} - + {/* - + */} diff --git a/src/components/PoDetail/PoDetail.tsx b/src/components/PoDetail/PoDetail.tsx index 3422b9a..6b39623 100644 --- a/src/components/PoDetail/PoDetail.tsx +++ b/src/components/PoDetail/PoDetail.tsx @@ -77,6 +77,7 @@ import dayjs, { Dayjs } from "dayjs"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import { DatePicker, LocalizationProvider, zhHK } from "@mui/x-date-pickers"; import { debounce } from "lodash"; +import LoadingComponent from "../General/LoadingComponent"; //import { useRouter } from "next/navigation"; @@ -115,7 +116,7 @@ const PoSearchList: React.FC<{ return ( - {t("Purchase Orders")} + {t("Purchase Order")} - - {filteredPoList.map((poItem, index) => ( -
- - onSelect(poItem)} - sx={{ - width: '100%', - "&.Mui-selected": { - backgroundColor: "primary.light", - "&:hover": { + {(filteredPoList.length > 0)? ( + + {filteredPoList.map((poItem, index) => ( +
+ + onSelect(poItem)} + sx={{ + width: '100%', + "&.Mui-selected": { backgroundColor: "primary.light", + "&:hover": { + backgroundColor: "primary.light", + }, }, - }, - }} - > - - {poItem.code} - - } - secondary={ - - {t(`${poItem.status.toLowerCase()}`)} - - } - /> - - - {index < filteredPoList.length - 1 && } -
- ))} -
+ }} + > + + {poItem.code} + + } + secondary={ + + {t(`${poItem.status.toLowerCase()}`)} + + } + /> +
+
+ {index < filteredPoList.length - 1 && } +
+ ))} +
) : ( + + ) + } {searchTerm && ( - {t("Found")} {filteredPoList.length} {t("of")} {poList.length} {t("items")} + {`${t("Found")} ${filteredPoList.length} ${t("Purchase Order")}`} + {/* {`${t("Found")} ${filteredPoList.length} of ${poList.length} ${t("Item")}`} */} )}
@@ -184,7 +190,7 @@ interface PolInputResult { const PoDetail: React.FC = ({ po, qc, warehouse }) => { const cameras = useContext(CameraContext); - console.log(cameras); + // console.log(cameras); const { t } = useTranslation("purchaseOrder"); const apiRef = useGridApiRef(); const [purchaseOrder, setPurchaseOrder] = useState({ ...po }); @@ -212,6 +218,8 @@ const PoDetail: React.FC = ({ po, qc, warehouse }) => { const router = useRouter(); const [poList, setPoList] = useState([]); const [selectedPoId, setSelectedPoId] = useState(po.id); + const [focusField, setFocusField] = useState(); + const currentPoId = searchParams.get('id'); const selectedIdsParam = searchParams.get('selectedIds'); // const [selectedRowId, setSelectedRowId] = useState(null); @@ -261,6 +269,7 @@ const PoDetail: React.FC = ({ po, qc, warehouse }) => { setProcessedQty(result.pol[0].processed); } } + // if (focusField) {console.log(focusField);focusField.focus();} } } catch (error) { console.error("Failed to fetch PO detail:", error); @@ -448,6 +457,8 @@ const PoDetail: React.FC = ({ po, qc, warehouse }) => { // setPolInputList(() => temp) }, 300), [rowIndex]); + // const [focusField, setFocusField] = useState(); + const purchaseToStockRatio = (row.stockUom.purchaseRatioN ?? 1) / (row.stockUom.purchaseRatioD ?? 1) * (row.stockUom.stockRatioD ?? 1) / (row.stockUom.stockRatioN ?? 1) return ( <> @@ -498,6 +509,7 @@ const PoDetail: React.FC = ({ po, qc, warehouse }) => { variant="outlined" defaultValue={polInputList[rowIndex]?.lotNo ?? ''} onChange={handleChange} + // onFocus={(e) => {setFocusField(e.target as HTMLInputElement);}} /> @@ -557,6 +569,8 @@ const PoDetail: React.FC = ({ po, qc, warehouse }) => { ); } +// ROW END + const [tabIndex, setTabIndex] = useState(0); const handleTabChange = useCallback>( (_e, newValue) => { diff --git a/src/components/PoDetail/PoInfoCard.tsx b/src/components/PoDetail/PoInfoCard.tsx index 7081556..678a28e 100644 --- a/src/components/PoDetail/PoInfoCard.tsx +++ b/src/components/PoDetail/PoInfoCard.tsx @@ -19,7 +19,7 @@ type Props = { po: PoResult; }; -const PoInfoCard: React.FC = async ( +const PoInfoCard: React.FC = ( { // id po diff --git a/src/components/PoDetail/QcFormVer2.tsx b/src/components/PoDetail/QcFormVer2.tsx index 2fe055e..09e2ee0 100644 --- a/src/components/PoDetail/QcFormVer2.tsx +++ b/src/components/PoDetail/QcFormVer2.tsx @@ -41,20 +41,20 @@ import { GridEditInputCell } from "@mui/x-data-grid"; import { StockInLine } from "@/app/api/po"; import { stockInLineStatusMap } from "@/app/utils/formatUtil"; import { fetchQcItemCheck, fetchQcResult } from "@/app/api/qc/actions"; -import { QcItemWithChecks } from "@/app/api/qc"; +import { QcItemWithChecks, QcData } 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 StockInFormVer2 from "./StockInFormVer2"; -import { dummyEscalationHistory, dummyQCData, QcData } from "./dummyQcTemplate"; +import { dummyEscalationHistory, dummyQCData } from "./dummyQcTemplate"; import { ModalFormInput } from "@/app/api/dashboard/actions"; import { escape } from "lodash"; import { PanoramaSharp } from "@mui/icons-material"; interface Props { - itemDetail: StockInLine; + itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] }; qc: QcItemWithChecks[]; disabled: boolean; qcItems: QcData[] @@ -88,8 +88,10 @@ const QcFormVer2: React.FC = ({ qc, itemDetail, disabled, qcItems, setQcI const [tabIndex, setTabIndex] = useState(0); const [rowSelectionModel, setRowSelectionModel] = useState(); const [escalationHistory, setEscalationHistory] = useState(dummyEscalationHistory); - const [qcResult, setQcResult] = useState(); + // const [qcResult, setQcResult] = useState(); const qcAccept = watch("qcAccept"); + const qcResult = watch("qcResult"); + console.log(qcResult); // const [qcAccept, setQcAccept] = useState(true); // const [qcItems, setQcItems] = useState(dummyQCData) @@ -184,28 +186,29 @@ const QcFormVer2: React.FC = ({ qc, itemDetail, disabled, qcItems, setQcI const qcColumns: GridColDef[] = [ { - field: "qcItem", + field: "code", headerName: t("qcItem"), flex: 2, renderCell: (params) => ( {params.value}
- {params.row.qcDescription}
+ {params.row.name}
), }, { - field: 'isPassed', + field: 'qcResult', headerName: t("qcResult"), flex: 1.5, renderCell: (params) => { - const currentValue = params.value; + const currentValue = params.row; + console.log(currentValue.row); return ( { const value = e.target.value; setQcItems((prev) => @@ -218,9 +221,9 @@ const QcFormVer2: React.FC = ({ qc, itemDetail, disabled, qcItems, setQcI value="true" control={} label="合格" - disabled={itemDetail.status.toLowerCase() == "completed"} + disabled={disabled} sx={{ - color: currentValue === true ? "green" : "inherit", + color: currentValue.isPassed === true ? "green" : "inherit", "& .Mui-checked": {color: "green"} }} /> @@ -228,9 +231,9 @@ const QcFormVer2: React.FC = ({ qc, itemDetail, disabled, qcItems, setQcI value="false" control={} label="不合格" - disabled={itemDetail.status.toLowerCase() == "completed"} + disabled={disabled} sx={{ - color: currentValue === false ? "red" : "inherit", + color: currentValue.isPassed === false ? "red" : "inherit", "& .Mui-checked": {color: "red"} }} /> @@ -249,7 +252,7 @@ const QcFormVer2: React.FC = ({ qc, itemDetail, disabled, qcItems, setQcI type="number" size="small" value={!params.row.isPassed? (params.value ?? '') : '0'} - disabled={params.row.isPassed || itemDetail.status.toLowerCase() == "completed"} + disabled={params.row.isPassed || disabled} onChange={(e) => { const v = e.target.value; const next = v === '' ? undefined : Number(v); @@ -257,6 +260,7 @@ const QcFormVer2: React.FC = ({ qc, itemDetail, disabled, qcItems, setQcI setQcItems((prev) => prev.map((r) => (r.id === params.id ? { ...r, failQty: next } : r)) ); + // setValue(`failQty`,failQty); }} onClick={(e) => e.stopPropagation()} onMouseDown={(e) => e.stopPropagation()} @@ -274,7 +278,7 @@ const QcFormVer2: React.FC = ({ qc, itemDetail, disabled, qcItems, setQcI { const remarks = e.target.value; // const next = v === '' ? undefined : Number(v); @@ -283,6 +287,9 @@ const QcFormVer2: React.FC = ({ qc, itemDetail, disabled, qcItems, setQcI prev.map((r) => (r.id === params.id ? { ...r, remarks: remarks } : r)) ); }} + // {...register(`qcResult.${params.row.rowIndex}.remarks`, { + // required: "remarks required!", + // })} onClick={(e) => e.stopPropagation()} onMouseDown={(e) => e.stopPropagation()} onKeyDown={(e) => e.stopPropagation()} @@ -293,11 +300,6 @@ const QcFormVer2: React.FC = ({ qc, itemDetail, disabled, qcItems, setQcI }, ] - useEffect(() => { - console.log(itemDetail); - - }, [itemDetail]); - // Set initial value for acceptQty useEffect(() => { if (itemDetail?.demandQty > 0) { //!== undefined) { @@ -308,9 +310,9 @@ const QcFormVer2: React.FC = ({ qc, itemDetail, disabled, qcItems, setQcI }, [itemDetail?.demandQty, itemDetail?.acceptedQty, setValue]); // const [openCollapse, setOpenCollapse] = useState(false) - const [isCollapsed, setIsCollapsed] = useState(false); + const [isCollapsed, setIsCollapsed] = useState(true); - const onFailedOpenCollapse = useCallback((qcItems: QcData[]) => { + const onFailedOpenCollapse = useCallback((qcItems: PurchaseQcResult[]) => { const isFailed = qcItems.some((qc) => !qc.isPassed) console.log(isFailed) if (isFailed) { @@ -327,10 +329,11 @@ const QcFormVer2: React.FC = ({ qc, itemDetail, disabled, qcItems, setQcI useEffect(() => { - console.log(itemDetail); + console.log("ItemDetail in QC:", itemDetail); }, [itemDetail]); + useEffect(() => { // onFailedOpenCollapse(qcItems) // This function is no longer needed }, [qcItems]); // Removed onFailedOpenCollapse from dependency array @@ -366,19 +369,10 @@ const QcFormVer2: React.FC = ({ qc, itemDetail, disabled, qcItems, setQcI /> */} - - {!qcAccept && ( - - - )} )} {tabIndex == 1 && ( @@ -425,7 +419,7 @@ const QcFormVer2: React.FC = ({ qc, itemDetail, disabled, qcItems, setQcI field.onChange(value); }} > - } label="接受" /> = ({ qc, itemDetail, disabled, qcItems, setQcI sx={{ width: '150px' }} value={qcAccept? accQty : 0 } defaultValue={accQty} - disabled={!qcAccept || itemDetail.status.toLowerCase() == "completed"} + disabled={!qcAccept || disabled} {...register("acceptQty", { required: "acceptQty required!", })} @@ -442,7 +436,7 @@ const QcFormVer2: React.FC = ({ qc, itemDetail, disabled, qcItems, setQcI helperText={errors.acceptQty?.message} /> - } sx={{"& .Mui-checked": {color: "red"}}} label="不接受及上報" /> @@ -451,6 +445,14 @@ const QcFormVer2: React.FC = ({ qc, itemDetail, disabled, qcItems, setQcI /> + {!qcAccept && ( + + + )} {/* {qcAccept && {t("Escalation Result")} diff --git a/src/components/PoDetail/QcStockInModalVer2.tsx b/src/components/PoDetail/QcStockInModalVer2.tsx index 0006b72..879f4cf 100644 --- a/src/components/PoDetail/QcStockInModalVer2.tsx +++ b/src/components/PoDetail/QcStockInModalVer2.tsx @@ -1,7 +1,7 @@ "use client"; import { StockInLine } from "@/app/api/po"; import { ModalFormInput, PurchaseQcResult, StockInLineEntry, updateStockInLine } from "@/app/api/po/actions"; -import { QcItemWithChecks } from "@/app/api/qc"; +import { QcItemWithChecks, QcData } from "@/app/api/qc"; import { Box, Button, @@ -19,7 +19,7 @@ import StockInForm from "./StockInForm"; import StockInFormVer2 from "./StockInFormVer2"; import QcFormVer2 from "./QcFormVer2"; import PutawayForm from "./PutawayForm"; -import { dummyPutawayLine, dummyQCData, QcData } from "./dummyQcTemplate"; +import { dummyPutawayLine, dummyQCData } from "./dummyQcTemplate"; import { useGridApiRef } from "@mui/x-data-grid"; import {submitDialogWithWarning} from "../Swal/CustomAlerts"; import { PurchaseQCInput, PutawayInput } from "@/app/api/dashboard/actions"; @@ -36,7 +36,7 @@ const style = { px: 5, pb: 10, display: "block", - width: { xs: "60%", sm: "60%", md: "60%" }, + width: { xs: "90%", sm: "90%", md: "90%" }, // height: { xs: "60%", sm: "60%", md: "60%" }, }; interface CommonProps extends Omit { @@ -113,6 +113,12 @@ const [qcItems, setQcItems] = useState(dummyQCData) setOpenPutaway(isPutaway); }, [open]) + + const [isCompleted, setIsCompleted] = useState(false); + + useEffect(() => { + setIsCompleted(itemDetail.status.toLowerCase() == "completed") + }, [itemDetail]); const [openPutaway, setOpenPutaway] = useState(false); const onOpenPutaway = useCallback(() => { @@ -155,21 +161,25 @@ const [qcItems, setQcItems] = useState(dummyQCData) // Get QC data from the shared form context const qcAccept = data.qcAccept; const acceptQty = data.acceptQty as number; + const qcResults = qcItems; + // const qcResults = isCompleted? data.qcResult as PurchaseQcResult[] : qcItems; // Validate QC data const validationErrors : string[] = []; // Check if all QC items have results - const itemsWithoutResult = qcItems.filter(item => item.isPassed === undefined); + const itemsWithoutResult = qcResults.filter(item => item.isPassed === undefined); if (itemsWithoutResult.length > 0) { - validationErrors.push(`${t("QC items without result")}: ${itemsWithoutResult.map(item => item.qcItem).join(', ')}`); + validationErrors.push(`${t("QC items without result")}`); + // validationErrors.push(`${t("QC items without result")}: ${itemsWithoutResult.map(item => item.code).join(', ')}`); } // Check if failed items have failed quantity - const failedItemsWithoutQty = qcItems.filter(item => + const failedItemsWithoutQty = qcResults.filter(item => item.isPassed === false && (!item.failQty || item.failQty <= 0) ); if (failedItemsWithoutQty.length > 0) { - validationErrors.push(`${t("Failed items must have failed quantity")}: ${failedItemsWithoutQty.map(item => item.qcItem).join(', ')}`); + 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 @@ -205,9 +215,9 @@ const [qcItems, setQcItems] = useState(dummyQCData) qcAccept: qcAccept? qcAccept : false, acceptQty: acceptQty? acceptQty : 0, - qcResult: qcItems.map(item => ({ + qcResult: qcResults.map(item => ({ qcItemId: item.id, - // qcItem: item.qcItem, + // code: item.code, // qcDescription: item.qcDescription, isPassed: item.isPassed? item.isPassed : false, failQty: (item.failQty && !item.isPassed) ? item.failQty : 0, @@ -329,7 +339,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) const acceptQty = formProps.watch("acceptedQty") - const checkQcIsPassed = useCallback((qcItems: QcData[]) => { + const checkQcIsPassed = useCallback((qcItems: PurchaseQcResult[]) => { const isPassed = qcItems.every((qc) => qc.isPassed); console.log(isPassed) if (isPassed) { @@ -343,7 +353,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) useEffect(() => { // maybe check if submitted before console.log(qcItems) - checkQcIsPassed(qcItems) + // checkQcIsPassed(qcItems) }, [qcItems, checkQcIsPassed]) return ( @@ -368,7 +378,7 @@ const [qcItems, setQcItems] = useState(dummyQCData)