From 6fdb5574a895dfbd95cca0e9b16d42024a566eb5 Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Wed, 20 Aug 2025 17:40:25 +0800 Subject: [PATCH 1/9] update --- src/components/PoDetail/PoDetail.tsx | 5 ++++- src/i18n/zh/common.json | 2 +- src/i18n/zh/inventory.json | 4 ++-- src/theme/devias-material-kit/components.ts | 9 ++++++++- 4 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/components/PoDetail/PoDetail.tsx b/src/components/PoDetail/PoDetail.tsx index 2c59d5f..3422b9a 100644 --- a/src/components/PoDetail/PoDetail.tsx +++ b/src/components/PoDetail/PoDetail.tsx @@ -447,6 +447,8 @@ const PoDetail: React.FC = ({ po, qc, warehouse }) => { } // setPolInputList(() => temp) }, 300), [rowIndex]); + + const purchaseToStockRatio = (row.stockUom.purchaseRatioN ?? 1) / (row.stockUom.purchaseRatioD ?? 1) * (row.stockUom.stockRatioD ?? 1) / (row.stockUom.stockRatioN ?? 1) return ( <> = ({ po, qc, warehouse }) => { {integerFormatter.format(row.qty)} {integerFormatter.format(processedQty)} {row.uom?.code} - {decimalFormatter.format(row.stockUom.stockQty)} + {/* {decimalFormatter.format(row.stockUom.stockQty)} */} + {decimalFormatter.format(row.stockInLine.filter((sil) => sil.purchaseOrderLineId === row.id).reduce((acc, cur) => acc + (cur.acceptedQty ?? 0),0) * purchaseToStockRatio)} {row.stockUom.stockUomCode} {/* {decimalFormatter.format(totalWeight)} {weightUnit} diff --git a/src/i18n/zh/common.json b/src/i18n/zh/common.json index 82f1be2..66be037 100644 --- a/src/i18n/zh/common.json +++ b/src/i18n/zh/common.json @@ -40,7 +40,7 @@ "non-consumables": "非消耗品", "fg": "成品", "sfg": "半成品", - "item": "物品", + "item": "貨品", "FG":"成品", "FG & Material Demand Forecast Detail":"成品及材料需求預測詳情", "View item In-out And inventory Ledger":"查看物料出入庫及庫存日誌", diff --git a/src/i18n/zh/inventory.json b/src/i18n/zh/inventory.json index ec54564..14a30a8 100644 --- a/src/i18n/zh/inventory.json +++ b/src/i18n/zh/inventory.json @@ -15,6 +15,6 @@ "Base UoM": "基本單位", "Lot No": "批號", "Expiry Date": "到期日", - "No items are selected yet.": "未選擇項目", - "Item selected": "已選擇項目" + "No items are selected yet.": "未選擇貨品", + "Item selected": "已選擇貨品" } \ No newline at end of file diff --git a/src/theme/devias-material-kit/components.ts b/src/theme/devias-material-kit/components.ts index 61e3f52..50595ad 100644 --- a/src/theme/devias-material-kit/components.ts +++ b/src/theme/devias-material-kit/components.ts @@ -314,7 +314,7 @@ const components: ThemeOptions["components"] = { styleOverrides: { root: { borderBottomColor: palette.divider, - padding: "1px 6px", + padding: "10px 6px", fontSize: defaultFontSize - 2, // padding: "15px 16px", // lineHeight: 1.5, @@ -342,6 +342,13 @@ const components: ThemeOptions["components"] = { }, }, }, + // MuiTableFooter: { + // styleOverrides: { + // root: { + // padding: "1px 6px", + // }, + // }, + // }, MuiTextField: { defaultProps: { variant: "filled", From 054b874ba9eab396336d899a0142f8ef0f16dc83 Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Thu, 21 Aug 2025 11:54:30 +0800 Subject: [PATCH 2/9] update escalation --- src/app/(main)/dashboard/page.tsx | 3 +++ src/app/api/dashboard/index.ts | 7 ++++--- src/components/DashboardPage/DashboardPage.tsx | 9 ++++++--- src/components/DashboardPage/DashboardWrapper.tsx | 15 ++++++++++----- 4 files changed, 23 insertions(+), 11 deletions(-) diff --git a/src/app/(main)/dashboard/page.tsx b/src/app/(main)/dashboard/page.tsx index ac1db4a..d259047 100644 --- a/src/app/(main)/dashboard/page.tsx +++ b/src/app/(main)/dashboard/page.tsx @@ -4,6 +4,7 @@ import { Suspense } from "react"; import { getServerI18n } from "@/i18n"; import DashboardPage from "@/components/DashboardPage"; import { SearchParams } from "@/app/utils/fetchUtil"; +import { fetchEscalationLogsByUser } from "@/app/api/escalation"; export const metadata: Metadata = { title: "Dashboard", @@ -14,6 +15,8 @@ type Props = {} & SearchParams; const Dashboard: React.FC = async ({ searchParams }) => { const { t } = await getServerI18n("dashboard"); + fetchEscalationLogsByUser() + return ( }> diff --git a/src/app/api/dashboard/index.ts b/src/app/api/dashboard/index.ts index 2bbb799..4b7ba4e 100644 --- a/src/app/api/dashboard/index.ts +++ b/src/app/api/dashboard/index.ts @@ -84,7 +84,8 @@ export const fetchPoWithStockInLines = cache(async (id: number) => { }); export const fetchIqcLogByUser = cache(async () => { - return serverFetchJson(`${BASE_API_URL}/supervisionApprovalLog/stock-in`, { - next: { tags: ["qcLog"] }, - }); + // return serverFetchJson(`${BASE_API_URL}/supervisionApprovalLog/stock-in`, { + // next: { tags: ["qcLog"] }, + // }); + return undefined; }); diff --git a/src/components/DashboardPage/DashboardPage.tsx b/src/components/DashboardPage/DashboardPage.tsx index 23c7a15..e675775 100644 --- a/src/components/DashboardPage/DashboardPage.tsx +++ b/src/components/DashboardPage/DashboardPage.tsx @@ -15,12 +15,15 @@ import OrderCompletionChart from "./chart/OrderCompletionChart"; import DashboardBox from "./Dashboardbox"; import CollapsibleCard from "./CollapsibleCard"; import SupervisorQcApproval, { IQCItems } from "./QC/SupervisorQcApproval"; +import { EscalationResult } from "@/app/api/escalation"; type Props = { - iqc: IQCItems[] + // iqc: IQCItems[] | undefined + escalationLogs: EscalationResult[] }; const DashboardPage: React.FC = ({ - iqc + // iqc, + escalationLogs }) => { const { t } = useTranslation("dashboard"); const router = useRouter(); @@ -31,7 +34,7 @@ const DashboardPage: React.FC = ({ - + diff --git a/src/components/DashboardPage/DashboardWrapper.tsx b/src/components/DashboardPage/DashboardWrapper.tsx index 4b35ed1..234ba23 100644 --- a/src/components/DashboardPage/DashboardWrapper.tsx +++ b/src/components/DashboardPage/DashboardWrapper.tsx @@ -4,7 +4,8 @@ import DashboardPage from "./DashboardPage"; import { Typography } from "@mui/material"; import { I18nProvider, getServerI18n } from "@/i18n"; import DashboardLoading from "./DashboardLoading"; -import { fetchIqcLogByUser } from "@/app/api/dashboard"; +import { fetchEscalationLogsByUser } from "@/app/api/escalation"; +// import { fetchIqcLogByUser } from "@/app/api/dashboard"; // export type SessionWithAbilities = { // abilities: string[] @@ -23,16 +24,20 @@ const DashboardWrapper: React.FC & SubComponents = async ({ }) => { const { t } = await getServerI18n("dashboard"); // const session: SessionWithAbilities = await getServerSession(authOptions) - const [iqcLog] = await Promise.all([ - fetchIqcLogByUser() + // const [iqcLog] = await Promise.all([ + // fetchIqcLogByUser() + // ]) + + const [escalationLogs] = await Promise.all([ + fetchEscalationLogsByUser() ]) - console.log(iqcLog) return ( <> {t("Dashboard")} From 29db5e657d738964d313d609629ce6b0e326bc15 Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Thu, 21 Aug 2025 11:55:06 +0800 Subject: [PATCH 3/9] update --- src/app/api/escalation/index.ts | 53 +++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 src/app/api/escalation/index.ts diff --git a/src/app/api/escalation/index.ts b/src/app/api/escalation/index.ts new file mode 100644 index 0000000..1c0d0f2 --- /dev/null +++ b/src/app/api/escalation/index.ts @@ -0,0 +1,53 @@ +"server only" + +import { convertObjToURLSearchParams } from "@/app/utils/commonUtil"; +import { serverFetchJson } from "@/app/utils/fetchUtil"; +import { BASE_API_URL } from "@/config/api"; +import { cache } from "react"; + +export interface EscalationResult { + id: number; + personInChargeDepartment?: string; + personInChargeName?: string; + personInChargeTitle?: string; + polId?: number; + poId?: number; + reason?: string; + personInChargeId?: number; + itemName?: string; + demandQty?: number; + acceptedQty?: number; + stockInLineId?: number; + stockOutLineId?: number; + qcFailCount?: number; + qcTotalCount?: number; + poCode?: string; + itemCode?: string; + dnDate?: number[]; + dnNo?: string; +} + +export const fetchEscalationLogsByStockInLines = cache(async(stockInLineIds: number[]) => { + const searchParams = convertObjToURLSearchParams({stockInLineIds: stockInLineIds}) + return serverFetchJson(`${BASE_API_URL}/escalationLog/stockInLines?${searchParams}`, + { + method: "GET", + headers: { "Content-Type": "application/json" }, + next: { + tags: ["escalationLogs"], + }, + }, + ); +}); + +export const fetchEscalationLogsByUser = cache(async() => { + return serverFetchJson(`${BASE_API_URL}/escalationLog/user`, + { + method: "GET", + headers: { "Content-Type": "application/json" }, + next: { + tags: ["escalationLogs"], + }, + }, + ); +}); \ No newline at end of file From 5e4336e1ebef1e4a566357e32228abf7a2d5cbff Mon Sep 17 00:00:00 2001 From: kelvinsuen Date: Thu, 21 Aug 2025 13:57:13 +0800 Subject: [PATCH 4/9] 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)
diff --git a/src/components/PoDetail/QcFormVer2.tsx b/src/components/PoDetail/QcFormVer2.tsx index 09e2ee0..37667a9 100644 --- a/src/components/PoDetail/QcFormVer2.tsx +++ b/src/components/PoDetail/QcFormVer2.tsx @@ -49,7 +49,7 @@ import EscalationComponent from "./EscalationComponent"; import QcDataGrid from "./QCDatagrid"; import StockInFormVer2 from "./StockInFormVer2"; import { dummyEscalationHistory, dummyQCData } from "./dummyQcTemplate"; -import { ModalFormInput } from "@/app/api/dashboard/actions"; +import { ModalFormInput } from "@/app/api/po/actions"; import { escape } from "lodash"; import { PanoramaSharp } from "@mui/icons-material"; @@ -197,7 +197,7 @@ const QcFormVer2: React.FC = ({ qc, itemDetail, disabled, qcItems, setQcI ), }, { - field: 'qcResult', + field: 'qcPassed', headerName: t("qcResult"), flex: 1.5, renderCell: (params) => { @@ -208,14 +208,15 @@ const QcFormVer2: React.FC = ({ qc, itemDetail, disabled, qcItems, setQcI { const value = e.target.value; setQcItems((prev) => - prev.map((r): QcData => (r.id === params.id ? { ...r, isPassed: value === "true" } : r)) + prev.map((r): QcData => (r.id === params.id ? { ...r, qcPassed: value === "true" } : r)) ); }} - name={`isPassed-${params.id}`} + name={`qcPassed-${params.id}`} > = ({ qc, itemDetail, disabled, qcItems, setQcI label="合格" disabled={disabled} sx={{ - color: currentValue.isPassed === true ? "green" : "inherit", + color: currentValue.qcPassed === true ? "green" : "inherit", "& .Mui-checked": {color: "green"} }} /> @@ -233,7 +234,7 @@ const QcFormVer2: React.FC = ({ qc, itemDetail, disabled, qcItems, setQcI label="不合格" disabled={disabled} sx={{ - color: currentValue.isPassed === false ? "red" : "inherit", + color: currentValue.qcPassed === false ? "red" : "inherit", "& .Mui-checked": {color: "red"} }} /> @@ -251,8 +252,8 @@ const QcFormVer2: React.FC = ({ qc, itemDetail, disabled, qcItems, setQcI { const v = e.target.value; const next = v === '' ? undefined : Number(v); @@ -313,7 +314,7 @@ const QcFormVer2: React.FC = ({ qc, itemDetail, disabled, qcItems, setQcI const [isCollapsed, setIsCollapsed] = useState(true); const onFailedOpenCollapse = useCallback((qcItems: PurchaseQcResult[]) => { - const isFailed = qcItems.some((qc) => !qc.isPassed) + const isFailed = qcItems.some((qc) => !qc.qcPassed) console.log(isFailed) if (isFailed) { setIsCollapsed(true) @@ -439,7 +440,11 @@ const QcFormVer2: React.FC = ({ qc, itemDetail, disabled, qcItems, setQcI } sx={{"& .Mui-checked": {color: "red"}}} - label="不接受及上報" /> + label="不接受" /> + } + sx={{"& .Mui-checked": {color: "blue"}}} + label="上報品檢結果" /> )} /> diff --git a/src/components/PoDetail/QcStockInModalVer2.tsx b/src/components/PoDetail/QcStockInModalVer2.tsx index 879f4cf..79997c9 100644 --- a/src/components/PoDetail/QcStockInModalVer2.tsx +++ b/src/components/PoDetail/QcStockInModalVer2.tsx @@ -1,6 +1,6 @@ "use client"; import { StockInLine } from "@/app/api/po"; -import { ModalFormInput, PurchaseQcResult, StockInLineEntry, updateStockInLine } from "@/app/api/po/actions"; +import { ModalFormInput, PurchaseQcResult, StockInLineEntry, updateStockInLine, PurchaseQCInput } from "@/app/api/po/actions"; import { QcItemWithChecks, QcData } from "@/app/api/qc"; import { Box, @@ -22,7 +22,6 @@ import PutawayForm from "./PutawayForm"; 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"; import { arrayToDateString, arrayToInputDateString, dayjsToInputDateString } from "@/app/utils/formatUtil"; import dayjs from "dayjs"; @@ -114,10 +113,11 @@ const [qcItems, setQcItems] = useState(dummyQCData) }, [open]) - const [isCompleted, setIsCompleted] = useState(false); + const [viewOnly, setViewOnly] = useState(false); useEffect(() => { - setIsCompleted(itemDetail.status.toLowerCase() == "completed") + const isViewOnly = itemDetail.status.toLowerCase() == "completed" || itemDetail.status.toLowerCase() == "rejected" + setViewOnly(isViewOnly) }, [itemDetail]); const [openPutaway, setOpenPutaway] = useState(false); @@ -162,12 +162,12 @@ const [qcItems, setQcItems] = useState(dummyQCData) const qcAccept = data.qcAccept; const acceptQty = data.acceptQty as number; const qcResults = qcItems; - // const qcResults = isCompleted? data.qcResult as PurchaseQcResult[] : qcItems; + // const qcResults = viewOnly? data.qcResult as PurchaseQcResult[] : qcItems; // Validate QC data const validationErrors : string[] = []; // Check if all QC items have results - const itemsWithoutResult = qcResults.filter(item => item.isPassed === undefined); + const itemsWithoutResult = qcResults.filter(item => item.qcPassed === undefined); if (itemsWithoutResult.length > 0) { validationErrors.push(`${t("QC items without result")}`); // validationErrors.push(`${t("QC items without result")}: ${itemsWithoutResult.map(item => item.code).join(', ')}`); @@ -175,7 +175,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) // Check if failed items have failed quantity const failedItemsWithoutQty = qcResults.filter(item => - item.isPassed === false && (!item.failQty || item.failQty <= 0) + item.qcPassed === false && (!item.failQty || item.failQty <= 0) ); if (failedItemsWithoutQty.length > 0) { validationErrors.push(`${t("Failed items must have failed quantity")}`); @@ -194,10 +194,16 @@ const [qcItems, setQcItems] = useState(dummyQCData) // Check if dates are input if (data.productionDate === undefined || data.productionDate == null) { - validationErrors.push("Production Date cannot be null!"); + validationErrors.push("請輸入生產日期!"); } if (data.expiryDate === undefined || data.expiryDate == null) { - validationErrors.push("Expiry Date cannot be null!"); + validationErrors.push("請輸入到期日!"); + } + if (!qcResults.every((qc) => qc.qcPassed) && qcAccept) { + validationErrors.push("有不合格檢查項目,無法收貨!"); + // submitDialogWithWarning(() => postStockInLineWithQc(qcData), t, {title:"有不合格檢查項目,確認接受收貨?", + // confirmButtonText: t("confirm putaway"), html: ""}); + // return; } if (validationErrors.length > 0) { @@ -219,8 +225,8 @@ const [qcItems, setQcItems] = useState(dummyQCData) qcItemId: item.id, // code: item.code, // qcDescription: item.qcDescription, - isPassed: item.isPassed? item.isPassed : false, - failQty: (item.failQty && !item.isPassed) ? item.failQty : 0, + 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 || '' })) @@ -228,42 +234,21 @@ const [qcItems, setQcItems] = useState(dummyQCData) // const qcData = data; console.log("QC Data for submission:", qcData); + + await postStockInLine(qcData); - if (!qcData.qcResult.every((qc) => qc.isPassed) && qcData.qcAccept) { - submitDialogWithWarning(() => postStockInLineWithQc(qcData), t, {title:"有不合格檢查項目,確認接受收貨?", - confirmButtonText: t("confirm putaway"), html: ""}); - return; + if (qcData.qcAccept) { + // submitDialogWithWarning(onOpenPutaway, t, {title:"Save success, confirm to proceed?", + // confirmButtonText: t("confirm putaway"), html: ""}); + onOpenPutaway(); + } else { + closeHandler({}, "backdropClick"); } - await postStockInLineWithQc(qcData); - // return; + return ; + }, [onOpenPutaway, qcItems], ); - - const postStockInLineWithQc = useCallback(async (qcData: PurchaseQCInput) => { - const args = { - ...qcData - // id: itemDetail.id, - // purchaseOrderId: itemDetail.purchaseOrderId, - // purchaseOrderLineId: itemDetail.purchaseOrderLineId, - // itemId: itemDetail.itemId, - // ...data, - // productionDate: productionDate, - // expiryDate: expiryDate, - // receiptDate: receiptDate, - } as ModalFormInput; - - await postStockInLine(args); - - if (qcData.qcAccept) { - // submitDialogWithWarning(onOpenPutaway, t, {title:"Save success, confirm to proceed?", - // confirmButtonText: t("confirm putaway"), html: ""}); - onOpenPutaway(); - } else { - closeHandler({}, "backdropClick"); - } - return ; - },[onOpenPutaway,closeHandler]); const postStockInLine = useCallback(async (args: ModalFormInput) => { const submitData = { @@ -340,7 +325,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) const acceptQty = formProps.watch("acceptedQty") const checkQcIsPassed = useCallback((qcItems: PurchaseQcResult[]) => { - const isPassed = qcItems.every((qc) => qc.isPassed); + const isPassed = qcItems.every((qc) => qc.qcPassed); console.log(isPassed) if (isPassed) { formProps.setValue("passingQty", acceptQty) @@ -378,7 +363,7 @@ const [qcItems, setQcItems] = useState(dummyQCData) } @@ -604,7 +606,7 @@ const closeNewModal = useCallback(() => { // color: "primary.main", // marginRight: 1, }} - onClick={handleNewQC(params.row.id, params)} + // onClick={handleNewQC(params.row.id, params)} color="inherit" key="edit" />, @@ -914,6 +916,7 @@ const closeNewModal = useCallback(() => { open={newOpen} onClose={closeNewModal} itemDetail={modalInfo} + handleMailTemplateForStockInLine={handleMailTemplateForStockInLine} /> ) diff --git a/src/components/PoDetail/QcStockInModalVer2.tsx b/src/components/PoDetail/QcStockInModalVer2.tsx index 1f0998b..8451db6 100644 --- a/src/components/PoDetail/QcStockInModalVer2.tsx +++ b/src/components/PoDetail/QcStockInModalVer2.tsx @@ -54,6 +54,7 @@ interface CommonProps extends Omit { qc?: QcItemWithChecks[]; warehouse?: any[]; // type: "qc" | "stockIn" | "escalation" | "putaway" | "reject"; + handleMailTemplateForStockInLine: (stockInLineId: number) => void; } interface Props extends CommonProps { itemDetail: StockInLine & { qcResult?: PurchaseQcResult[] }; @@ -69,6 +70,7 @@ const PoQcStockInModalVer2: React.FC = ({ setItemDetail, qc, warehouse, + handleMailTemplateForStockInLine, }) => { const { t, From a6741df1a50bdf691e7e4f3a3faa808e90f721ee Mon Sep 17 00:00:00 2001 From: "cyril.tsui" Date: Mon, 25 Aug 2025 12:48:54 +0800 Subject: [PATCH 9/9] rename --- src/app/api/escalation/index.ts | 10 +++++----- .../DashboardPage/escalation/EscalationLogTable.tsx | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/app/api/escalation/index.ts b/src/app/api/escalation/index.ts index abfb30a..5c0e7d4 100644 --- a/src/app/api/escalation/index.ts +++ b/src/app/api/escalation/index.ts @@ -7,14 +7,14 @@ import { cache } from "react"; export interface EscalationResult { id: number; - personInCharge?: string; - personInChargeDepartment?: string; - personInChargeName?: string; - personInChargeTitle?: string; + handler?: string; + handlerDepartment?: string; + handlerName?: string; + handlerTitle?: string; polId?: number; poId?: number; reason?: string; - personInChargeId?: number; + handlerId?: number; itemName?: string; demandQty?: number; acceptedQty?: number; diff --git a/src/components/DashboardPage/escalation/EscalationLogTable.tsx b/src/components/DashboardPage/escalation/EscalationLogTable.tsx index a71e89e..62f1040 100644 --- a/src/components/DashboardPage/escalation/EscalationLogTable.tsx +++ b/src/components/DashboardPage/escalation/EscalationLogTable.tsx @@ -61,7 +61,7 @@ const EscalationLogTable: React.FC = ({ const columns = useMemo[]>( () => [ { - name: "personInCharge", + name: "handler", label: t("Responsible for handling colleagues") }, {