diff --git a/src/app/api/bom/client.ts b/src/app/api/bom/client.ts index 8d1db363..05c7075c 100644 --- a/src/app/api/bom/client.ts +++ b/src/app/api/bom/client.ts @@ -107,6 +107,18 @@ export async function fetchBomComboClient(options?: { ); return response.data; } + + /** Update free-text craft details (工藝明細) on BOM. */ + export async function updateBomDetailsClient( + id: number, + details: string | null, + ): Promise<{ id: number; details: string | null }> { + const response = await axiosInstance.put<{ id: number; details: string | null }>( + `${NEXT_PUBLIC_API_URL}/bom/${id}/details`, + { details }, + ); + return response.data; + } export type BomExcelCheckProgress = { batchId: string; totalFiles: number; diff --git a/src/app/api/jo/index.ts b/src/app/api/jo/index.ts index 67bd46e7..73e845d3 100644 --- a/src/app/api/jo/index.ts +++ b/src/app/api/jo/index.ts @@ -39,6 +39,8 @@ export interface JobOrder { stockInLineStatus?: string; silHandlerId?: number; lotNo?: string; + /** Standard BOM process minutes (prep + duration + post). */ + estimatedMinutes?: number | null; } export interface Machine { diff --git a/src/components/JoSearch/JoSearch.tsx b/src/components/JoSearch/JoSearch.tsx index c7ed6376..e8d271ac 100644 --- a/src/components/JoSearch/JoSearch.tsx +++ b/src/components/JoSearch/JoSearch.tsx @@ -459,6 +459,22 @@ const JoSearch: React.FC = ({ defaultInputs, bomCombo, printerCombo, jobT return row.item?.uom ? t(row.item.uom.udfudesc) : '-' } }, + { + name: "estimatedMinutes" as keyof JobOrder, + label: t("Estimated Process Time"), + align: "left", + headerAlign: "left", + renderCell: (row) => { + const mins = row.estimatedMinutes; + if (mins == null || mins <= 0) return "—"; + const hours = Math.floor(mins / 60); + const minutes = mins % 60; + const parts: string[] = []; + if (hours > 0) parts.push(t("Duration hours", { count: hours })); + if (minutes > 0 || hours === 0) parts.push(t("Duration minutes", { count: minutes })); + return parts.join(" "); + } + }, { name: "stockStatus" as keyof JobOrder, label: t("BOM Status"), diff --git a/src/components/JoWorkbench/JoWorkbenchSearch.tsx b/src/components/JoWorkbench/JoWorkbenchSearch.tsx index def91204..ca13ab8e 100644 --- a/src/components/JoWorkbench/JoWorkbenchSearch.tsx +++ b/src/components/JoWorkbench/JoWorkbenchSearch.tsx @@ -460,6 +460,22 @@ const JoWorkbenchSearch: React.FC = ({ defaultInputs, bomCombo, printerCo return row.item?.uom ? t(row.item.uom.udfudesc) : '-' } }, + { + name: "estimatedMinutes" as keyof JobOrder, + label: t("Estimated Process Time"), + align: "left", + headerAlign: "left", + renderCell: (row) => { + const mins = row.estimatedMinutes; + if (mins == null || mins <= 0) return "—"; + const hours = Math.floor(mins / 60); + const minutes = mins % 60; + const parts: string[] = []; + if (hours > 0) parts.push(t("Duration hours", { count: hours })); + if (minutes > 0 || hours === 0) parts.push(t("Duration minutes", { count: minutes })); + return parts.join(" "); + } + }, { name: "stockStatus" as keyof JobOrder, label: t("BOM Status"), diff --git a/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx b/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx index 78818a53..bd773ae6 100644 --- a/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx +++ b/src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx @@ -27,6 +27,7 @@ import { fetchProductProcessesByJobOrderId , setJobOrderHidden, updateProductPro import ProductionProcessDetail from "./ProductionProcessDetail"; import { BomCombo } from "@/app/api/bom"; import { fetchBomCombo } from "@/app/api/bom/index"; +import { updateBomDetailsClient } from "@/app/api/bom/client"; import dayjs from "dayjs"; import { OUTPUT_DATE_FORMAT, integerFormatter, arrayToDateString } from "@/app/utils/formatUtil"; import StyledDataGrid from "../StyledDataGrid/StyledDataGrid"; @@ -44,6 +45,7 @@ import EditIcon from "@mui/icons-material/Edit"; import { DatePicker, LocalizationProvider } from "@mui/x-date-pickers"; import { AdapterDayjs } from "@mui/x-date-pickers/AdapterDayjs"; import { dayjsToDateString } from "@/app/utils/formatUtil"; +import { msg } from "../Swal/CustomAlerts"; @@ -77,6 +79,8 @@ const [reqQtyMultiplier, setReqQtyMultiplier] = useState(1); const [selectedBomForReqQty, setSelectedBomForReqQty] = useState(null); const [bomCombo, setBomCombo] = useState([]); const [showBaseQty, setShowBaseQty] = useState(false); +const [bomDetailsText, setBomDetailsText] = useState(""); +const [savingBomDetails, setSavingBomDetails] = useState(false); const fetchData = useCallback(async () => { setLoading(true); @@ -86,6 +90,7 @@ const [showBaseQty, setShowBaseQty] = useState(false); const firstProcess = data[0]; setProcessData(firstProcess); setJobOrderLines((firstProcess as any).jobOrderLines || []); + setBomDetailsText((firstProcess as any).bomDetails ?? ""); } } catch (error) { console.error("Error loading data:", error); @@ -93,6 +98,27 @@ const [showBaseQty, setShowBaseQty] = useState(false); setLoading(false); } }, [jobOrderId]); + + const handleSaveBomDetails = useCallback(async () => { + const bomId = processData?.bomId as number | undefined; + if (!bomId) { + msg(t("update failed")); + return; + } + setSavingBomDetails(true); + try { + const result = await updateBomDetailsClient(bomId, bomDetailsText); + setProcessData((prev: any) => + prev ? { ...prev, bomDetails: result.details ?? "" } : prev, + ); + msg(t("update success")); + } catch (e) { + console.error(e); + msg(t("update failed")); + } finally { + setSavingBomDetails(false); + } + }, [bomDetailsText, processData?.bomId, t]); const toggleBaseQty = useCallback(() => { setShowBaseQty(prev => !prev); }, []); @@ -456,52 +482,6 @@ const handleRelease = useCallback(async (jobOrderId: number) => { ); - const productionProcessesLineRemarkTableColumns: GridColDef[] = [ - { - field: "seqNo", - headerName: t("SEQ"), - flex: 0.2, - align: "left", - headerAlign: "left", - type: "number", - renderCell: (params) => { - return {params.value}; - }, - }, - { - field: "description", - headerName: t("Remark"), - flex: 1, - align: "left", - headerAlign: "left", - renderCell: (params) => { - return( - -   - {params.value || ""} - -   - - ) - - }, - }, - - ]; - const productionProcessesLineRemarkTableRows = - processData?.productProcessLines?.map((line: any) => ({ - id: line.seqNo, - seqNo: line.seqNo, - - description: line.description ?? "", - - - - })) ?? []; - - - - const pickTableColumns: GridColDef[] = [ { field: "id", @@ -649,7 +629,7 @@ const handleRelease = useCallback(async (jobOrderId: number) => { return ( - {t("N/A")} + {t("No pick required")} ); @@ -687,7 +667,7 @@ const handleRelease = useCallback(async (jobOrderId: number) => { if (isExcludedFromPickStock(params.row.type)) { return ( - {t("N/A")} + {t("No pick required")} ); } @@ -764,32 +744,44 @@ const handleRelease = useCallback(async (jobOrderId: number) => { const ProductionProcessesLineRemarkTableContent = () => ( - 'auto'} - hideFooter={false} // ✅ Ensure footer is visible - /> + + + + setBomDetailsText(e.target.value)} + placeholder={t("Production Process Line Remark")} + InputLabelProps={{ shrink: false }} + sx={{ + "& .MuiInputBase-input::placeholder": { + color: "text.secondary", + opacity: 1, + }, + "& .MuiInputBase-input": { + color: "text.primary", + }, + }} + /> + + + + + + ); @@ -807,7 +799,7 @@ const handleRelease = useCallback(async (jobOrderId: number) => { - + diff --git a/src/components/ProductionProcess/ProductionProcessPage.tsx b/src/components/ProductionProcess/ProductionProcessPage.tsx index 19e6d2fe..5b0b0a0b 100644 --- a/src/components/ProductionProcess/ProductionProcessPage.tsx +++ b/src/components/ProductionProcess/ProductionProcessPage.tsx @@ -13,7 +13,6 @@ import JobPickExecutionsecondscan from "@/components/Jodetail/JobPickExecutionse import JobProcessStatus from "@/components/ProductionProcess/JobProcessStatus"; import OperatorKpiDashboard from "@/components/ProductionProcess/OperatorKpiDashboard"; import EquipmentStatusDashboard from "@/components/ProductionProcess/EquipmentStatusDashboard"; -import DrinkProductionQtyDashboard from "@/components/ProductionProcess/DrinkProductionQtyDashboard"; import JobOrderOpsTable from "@/components/ProductionProcess/JobOrderOpsTable"; import type { PrinterCombo } from "@/app/api/settings/printer"; import { useTranslation } from "react-i18next"; @@ -30,8 +29,7 @@ type PageTab = | "ops" | "jobProcessStatus" | "operatorKpi" - | "equipmentStatus" - | "drinkQty"; + | "equipmentStatus"; /** * FP-MTMS Version Checklist | Functions Ref. No. 26 | v1.0.0 | 2026-07-20 @@ -169,7 +167,6 @@ const ProductionProcessPage: React.FC = ({ printerCo - {pageTab === "list" && ( @@ -211,9 +208,6 @@ const ProductionProcessPage: React.FC = ({ printerCo {pageTab === "equipmentStatus" && ( )} - {pageTab === "drinkQty" && ( - - )}