| @@ -504,7 +504,7 @@ export default function ReportPage() { | |||||
| setLoading={setLoading} | setLoading={setLoading} | ||||
| reportTitle={currentReport.title} | reportTitle={currentReport.title} | ||||
| /> | /> | ||||
| ) : currentReport.id === 'rep-013' || currentReport.id === 'rep-009' || currentReport.id === 'rep-012' ? ( | |||||
| ) : currentReport.id === 'rep-013' || currentReport.id === 'rep-009' || currentReport.id === 'rep-012' || currentReport.id === 'rep-004' || currentReport.id === 'rep-007' || currentReport.id === 'rep-008' || currentReport.id === 'rep-011' ? ( | |||||
| <> | <> | ||||
| <Button | <Button | ||||
| variant="contained" | variant="contained" | ||||
| @@ -352,6 +352,8 @@ export interface AllJoborderProductProcessInfoResponse { | |||||
| uom: string; | uom: string; | ||||
| isDrink?: boolean | null; | isDrink?: boolean | null; | ||||
| stockInLineId: number; | stockInLineId: number; | ||||
| /** Stock-in-line current status (e.g. receiving/received/partially_completed/completed/rejected). */ | |||||
| stockInLineStatus?: string | null; | |||||
| jobOrderCode: string; | jobOrderCode: string; | ||||
| productProcessLineCount: number; | productProcessLineCount: number; | ||||
| FinishedProductProcessLineCount: number; | FinishedProductProcessLineCount: number; | ||||
| @@ -798,6 +800,8 @@ export const fetchJoborderProductProcessesPage = cache(async (params: { | |||||
| qcReady?: boolean | null; | qcReady?: boolean | null; | ||||
| type?: string | null; | type?: string | null; | ||||
| includePutaway?: boolean | null; | includePutaway?: boolean | null; | ||||
| /** all | completed | notCompleted */ | |||||
| putawayStatus?: string | null; | |||||
| page?: number; | page?: number; | ||||
| size?: number; | size?: number; | ||||
| }) => { | }) => { | ||||
| @@ -808,6 +812,7 @@ export const fetchJoborderProductProcessesPage = cache(async (params: { | |||||
| bomIds, | bomIds, | ||||
| qcReady, | qcReady, | ||||
| includePutaway, | includePutaway, | ||||
| putawayStatus, | |||||
| type, | type, | ||||
| page = 0, | page = 0, | ||||
| size = 50, | size = 50, | ||||
| @@ -825,6 +830,7 @@ export const fetchJoborderProductProcessesPage = cache(async (params: { | |||||
| if (includePutaway !== undefined && includePutaway !== null) { | if (includePutaway !== undefined && includePutaway !== null) { | ||||
| queryParts.push(`includePutaway=${includePutaway}`); | queryParts.push(`includePutaway=${includePutaway}`); | ||||
| } | } | ||||
| if (putawayStatus) queryParts.push(`putawayStatus=${encodeURIComponent(putawayStatus)}`); | |||||
| queryParts.push(`page=${page}`); | queryParts.push(`page=${page}`); | ||||
| queryParts.push(`size=${size}`); | queryParts.push(`size=${size}`); | ||||
| @@ -77,7 +77,6 @@ import QrCodeIcon from "@mui/icons-material/QrCode"; | |||||
| import { useQrCodeScannerContext } from "../QrCodeScannerProvider/QrCodeScannerProvider"; | import { useQrCodeScannerContext } from "../QrCodeScannerProvider/QrCodeScannerProvider"; | ||||
| import { useSession } from "next-auth/react"; | import { useSession } from "next-auth/react"; | ||||
| import { SessionWithTokens } from "@/config/authConfig"; | import { SessionWithTokens } from "@/config/authConfig"; | ||||
| import { AUTH } from "@/authorities"; | |||||
| import { fetchStockInLineInfo } from "@/app/api/po/actions"; | import { fetchStockInLineInfo } from "@/app/api/po/actions"; | ||||
| import GoodPickExecutionForm from "./GoodPickExecutionForm"; | import GoodPickExecutionForm from "./GoodPickExecutionForm"; | ||||
| import FGPickOrderCard from "./FGPickOrderCard"; | import FGPickOrderCard from "./FGPickOrderCard"; | ||||
| @@ -687,8 +686,6 @@ const PickExecution: React.FC<Props> = ({ | |||||
| const { t } = useTranslation("pickOrder"); | const { t } = useTranslation("pickOrder"); | ||||
| const router = useRouter(); | const router = useRouter(); | ||||
| const { data: session } = useSession() as { data: SessionWithTokens | null }; | const { data: session } = useSession() as { data: SessionWithTokens | null }; | ||||
| const abilities = session?.abilities ?? session?.user?.abilities ?? []; | |||||
| const isAdmin = abilities.some((a) => String(a).trim() === AUTH.ADMIN); | |||||
| const [doPickOrderDetail, setDoPickOrderDetail] = | const [doPickOrderDetail, setDoPickOrderDetail] = | ||||
| useState<DoPickOrderDetail | null>(null); | useState<DoPickOrderDetail | null>(null); | ||||
| const [selectedPickOrderId, setSelectedPickOrderId] = useState<number | null>( | const [selectedPickOrderId, setSelectedPickOrderId] = useState<number | null>( | ||||
| @@ -783,6 +780,25 @@ const PickExecution: React.FC<Props> = ({ | |||||
| useState<any | null>(null); | useState<any | null>(null); | ||||
| const [fgPickOrders, setFgPickOrders] = useState<FGPickOrderResponse[]>([]); | const [fgPickOrders, setFgPickOrders] = useState<FGPickOrderResponse[]>([]); | ||||
| const [fgPickOrdersLoading, setFgPickOrdersLoading] = useState(false); | const [fgPickOrdersLoading, setFgPickOrdersLoading] = useState(false); | ||||
| const lotFloorPrefixFilter = useMemo(() => { | |||||
| const storeId = String(fgPickOrders?.[0]?.storeId ?? "") | |||||
| .trim() | |||||
| .toUpperCase() | |||||
| .replace(/\s/g, ""); | |||||
| // e.g. "2/F" -> "2F-", "4/F" -> "4F-" | |||||
| const floorKey = storeId.replace(/\//g, ""); | |||||
| return floorKey ? `${floorKey}-` : ""; | |||||
| }, [fgPickOrders]); | |||||
| const defaultLabelPrinterName = useMemo(() => { | |||||
| const storeId = String(fgPickOrders?.[0]?.storeId ?? "") | |||||
| .trim() | |||||
| .toUpperCase() | |||||
| .replace(/\s/g, ""); | |||||
| const floorKey = storeId.replace(/\//g, ""); | |||||
| if (floorKey === "2F") return "Label機 2F A+B"; | |||||
| if (floorKey === "4F") return "Label機 4F 乾貨 C, D"; | |||||
| return undefined; | |||||
| }, [fgPickOrders]); | |||||
| // Add these missing state variables after line 352 | // Add these missing state variables after line 352 | ||||
| const [isManualScanning, setIsManualScanning] = useState<boolean>(false); | const [isManualScanning, setIsManualScanning] = useState<boolean>(false); | ||||
| // Track processed QR codes by itemId+stockInLineId combination for better lot confirmation handling | // Track processed QR codes by itemId+stockInLineId combination for better lot confirmation handling | ||||
| @@ -1690,7 +1706,7 @@ const PickExecution: React.FC<Props> = ({ | |||||
| return; | return; | ||||
| } | } | ||||
| if (switchedToUnavailable && isAdmin) { | |||||
| if (switchedToUnavailable) { | |||||
| const itemId = Number(sel?.itemId ?? exp?.itemId); | const itemId = Number(sel?.itemId ?? exp?.itemId); | ||||
| const stockInLineId = Number(newStockInLineId); | const stockInLineId = Number(newStockInLineId); | ||||
| if (Number.isFinite(itemId) && Number.isFinite(stockInLineId)) { | if (Number.isFinite(itemId) && Number.isFinite(stockInLineId)) { | ||||
| @@ -1745,7 +1761,6 @@ const PickExecution: React.FC<Props> = ({ | |||||
| resetScan, | resetScan, | ||||
| clearLotConfirmationState, | clearLotConfirmationState, | ||||
| t, | t, | ||||
| isAdmin, | |||||
| ], | ], | ||||
| ); | ); | ||||
| @@ -2138,7 +2153,7 @@ const PickExecution: React.FC<Props> = ({ | |||||
| const byLotId = new Map<number, any>(); | const byLotId = new Map<number, any>(); | ||||
| const byLotNo = new Map<string, any[]>(); | const byLotNo = new Map<string, any[]>(); | ||||
| const byStockInLineId = new Map<number, any[]>(); | const byStockInLineId = new Map<number, any[]>(); | ||||
| // Cache active lots separately to avoid filtering on every scan | // Cache active lots separately to avoid filtering on every scan | ||||
| const activeLotsByItemId = new Map<number, any[]>(); | const activeLotsByItemId = new Map<number, any[]>(); | ||||
| const rejectedStatuses = new Set(["rejected"]); | const rejectedStatuses = new Set(["rejected"]); | ||||
| @@ -5055,9 +5070,12 @@ const PickExecution: React.FC<Props> = ({ | |||||
| setLotLabelPrintReminderText(null); | setLotLabelPrintReminderText(null); | ||||
| }} | }} | ||||
| initialPayload={lotLabelPrintInitialPayload} | initialPayload={lotLabelPrintInitialPayload} | ||||
| defaultPrinterName="Label機 2F A+B" | |||||
| defaultPrinterName={defaultLabelPrinterName} | |||||
| hideScanSection | hideScanSection | ||||
| reminderText={lotLabelPrintReminderText ?? undefined} | reminderText={lotLabelPrintReminderText ?? undefined} | ||||
| statusTitleText="此批號的已用完/已過期" | |||||
| warehouseCodePrefixFilter={lotFloorPrefixFilter} | |||||
| hideTriggeredLot | |||||
| /> | /> | ||||
| </FormProvider> | </FormProvider> | ||||
| </TestQrCodeProvider> | </TestQrCodeProvider> | ||||
| @@ -75,6 +75,12 @@ export interface LotLabelPrintModalProps { | |||||
| hideScanSection?: boolean; | hideScanSection?: boolean; | ||||
| /** 額外提醒(顯示在最上方) */ | /** 額外提醒(顯示在最上方) */ | ||||
| reminderText?: string; | reminderText?: string; | ||||
| /** 額外標題(顯示在最上方,reminderText 之下) */ | |||||
| statusTitleText?: string; | |||||
| /** 只顯示特定倉位前綴(例如 "2F-") */ | |||||
| warehouseCodePrefixFilter?: string; | |||||
| /** 不顯示觸發視窗的批號(analysis.scanned) */ | |||||
| hideTriggeredLot?: boolean; | |||||
| } | } | ||||
| function safeParseScanPayload(raw: string): ScanPayload | null { | function safeParseScanPayload(raw: string): ScanPayload | null { | ||||
| @@ -115,6 +121,9 @@ const LotLabelPrintModal: React.FC<LotLabelPrintModalProps> = ({ | |||||
| defaultPrinterName, | defaultPrinterName, | ||||
| hideScanSection, | hideScanSection, | ||||
| reminderText, | reminderText, | ||||
| statusTitleText, | |||||
| warehouseCodePrefixFilter, | |||||
| hideTriggeredLot, | |||||
| }) => { | }) => { | ||||
| const scanInputRef = useRef<HTMLInputElement | null>(null); | const scanInputRef = useRef<HTMLInputElement | null>(null); | ||||
| const [scanInput, setScanInput] = useState(""); | const [scanInput, setScanInput] = useState(""); | ||||
| @@ -126,6 +135,7 @@ const LotLabelPrintModal: React.FC<LotLabelPrintModalProps> = ({ | |||||
| const [analysisLoading, setAnalysisLoading] = useState(false); | const [analysisLoading, setAnalysisLoading] = useState(false); | ||||
| const [analysis, setAnalysis] = useState<QrCodeAnalysisResponse | null>(null); | const [analysis, setAnalysis] = useState<QrCodeAnalysisResponse | null>(null); | ||||
| const [lastPayload, setLastPayload] = useState<ScanPayload | null>(null); | |||||
| const [printQty, setPrintQty] = useState(1); | const [printQty, setPrintQty] = useState(1); | ||||
| const [printingLotLineId, setPrintingLotLineId] = useState<number | null>( | const [printingLotLineId, setPrintingLotLineId] = useState<number | null>( | ||||
| @@ -234,6 +244,7 @@ const LotLabelPrintModal: React.FC<LotLabelPrintModalProps> = ({ | |||||
| return; | return; | ||||
| } | } | ||||
| setLastPayload(payload); | |||||
| setScanError(null); | setScanError(null); | ||||
| setAnalysisLoading(true); | setAnalysisLoading(true); | ||||
| try { | try { | ||||
| @@ -279,6 +290,19 @@ const LotLabelPrintModal: React.FC<LotLabelPrintModalProps> = ({ | |||||
| await analyzePayload(payload); | await analyzePayload(payload); | ||||
| }, [scanInput, analyzePayload]); | }, [scanInput, analyzePayload]); | ||||
| const handleRefreshLots = useCallback(async () => { | |||||
| const payload = lastPayload ?? safeParseScanPayload(scanInput.trim()); | |||||
| if (!payload) { | |||||
| setSnackbar({ | |||||
| open: true, | |||||
| message: "請先掃碼或查詢一次,才可刷新批號清單。", | |||||
| severity: "info", | |||||
| }); | |||||
| return; | |||||
| } | |||||
| await analyzePayload(payload); | |||||
| }, [analyzePayload, lastPayload, scanInput]); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| if (!open) return; | if (!open) return; | ||||
| if (!initialPayload) return; | if (!initialPayload) return; | ||||
| @@ -310,14 +334,22 @@ const LotLabelPrintModal: React.FC<LotLabelPrintModalProps> = ({ | |||||
| : null; | : null; | ||||
| const merged = [ | const merged = [ | ||||
| ...(scannedLot ? [scannedLot] : []), | |||||
| ...(!hideTriggeredLot && scannedLot ? [scannedLot] : []), | |||||
| ...list | ...list | ||||
| .filter((x) => x.inventoryLotLineId !== scannedLotLineId) | .filter((x) => x.inventoryLotLineId !== scannedLotLineId) | ||||
| .map((x) => ({ ...x, _scanned: false as const })), | .map((x) => ({ ...x, _scanned: false as const })), | ||||
| ]; | ]; | ||||
| return merged; | return merged; | ||||
| }, [analysis]); | |||||
| }, [analysis, hideTriggeredLot]); | |||||
| const filteredLots = useMemo(() => { | |||||
| const prefix = String(warehouseCodePrefixFilter ?? "").trim(); | |||||
| if (!prefix) return availableLots; | |||||
| return availableLots.filter((lot) => | |||||
| String(lot.warehouseCode ?? "").startsWith(prefix), | |||||
| ); | |||||
| }, [availableLots, warehouseCodePrefixFilter]); | |||||
| const selectedPrinter = useMemo(() => { | const selectedPrinter = useMemo(() => { | ||||
| if (selectedPrinterId === "") return null; | if (selectedPrinterId === "") return null; | ||||
| @@ -390,6 +422,14 @@ const LotLabelPrintModal: React.FC<LotLabelPrintModalProps> = ({ | |||||
| <DialogTitle>批號標籤列印</DialogTitle> | <DialogTitle>批號標籤列印</DialogTitle> | ||||
| <DialogContent> | <DialogContent> | ||||
| <Stack spacing={2} sx={{ mt: 1 }}> | <Stack spacing={2} sx={{ mt: 1 }}> | ||||
| {statusTitleText ? ( | |||||
| <Typography | |||||
| variant="h6" | |||||
| sx={{ fontWeight: 800, color: "error.main" }} | |||||
| > | |||||
| {statusTitleText} | |||||
| </Typography> | |||||
| ) : null} | |||||
| {reminderText ? ( | {reminderText ? ( | ||||
| <Alert severity="warning">{reminderText}</Alert> | <Alert severity="warning">{reminderText}</Alert> | ||||
| ) : null} | ) : null} | ||||
| @@ -485,13 +525,13 @@ const LotLabelPrintModal: React.FC<LotLabelPrintModalProps> = ({ | |||||
| <Button | <Button | ||||
| variant="outlined" | variant="outlined" | ||||
| onClick={() => void loadPrinters()} | |||||
| disabled={printersLoading} | |||||
| onClick={() => void handleRefreshLots()} | |||||
| disabled={analysisLoading} | |||||
| > | > | ||||
| {printersLoading ? ( | |||||
| {analysisLoading ? ( | |||||
| <CircularProgress size={18} /> | <CircularProgress size={18} /> | ||||
| ) : ( | ) : ( | ||||
| "重新載入印表機" | |||||
| "刷新批號清單" | |||||
| )} | )} | ||||
| </Button> | </Button> | ||||
| @@ -512,13 +552,13 @@ const LotLabelPrintModal: React.FC<LotLabelPrintModalProps> = ({ | |||||
| 品號:{analysis.itemCode} {analysis.itemName} | 品號:{analysis.itemCode} {analysis.itemName} | ||||
| </Typography> | </Typography> | ||||
| {availableLots.length === 0 ? ( | |||||
| {filteredLots.length === 0 ? ( | |||||
| <Alert severity="warning"> | <Alert severity="warning"> | ||||
| 找不到可用批號(availableQty > 0)。 | |||||
| 找不到該樓層有可用批號(availableQty > 0)。 | |||||
| </Alert> | </Alert> | ||||
| ) : ( | ) : ( | ||||
| <Stack spacing={1}> | <Stack spacing={1}> | ||||
| {availableLots.map((lot) => { | |||||
| {filteredLots.map((lot) => { | |||||
| const isPrinting = | const isPrinting = | ||||
| printingLotLineId === lot.inventoryLotLineId; | printingLotLineId === lot.inventoryLotLineId; | ||||
| const loc = String(lot.warehouseCode ?? "").trim(); | const loc = String(lot.warehouseCode ?? "").trim(); | ||||
| @@ -60,6 +60,9 @@ interface ProductProcessListProps { | |||||
| onSelectMatchingStock: (jobOrderId: number|undefined, productProcessId: number|undefined,pickOrderId: number|undefined) => void; | onSelectMatchingStock: (jobOrderId: number|undefined, productProcessId: number|undefined,pickOrderId: number|undefined) => void; | ||||
| printerCombo: PrinterCombo[]; | printerCombo: PrinterCombo[]; | ||||
| qcReady: boolean; | qcReady: boolean; | ||||
| includePutaway?: boolean | null; | |||||
| /** all | completed | notCompleted */ | |||||
| putawayStatus?: string | null; | |||||
| listPersistedState: ProductionProcessListPersistedState; | listPersistedState: ProductionProcessListPersistedState; | ||||
| onListPersistedStateChange: React.Dispatch< | onListPersistedStateChange: React.Dispatch< | ||||
| React.SetStateAction<ProductionProcessListPersistedState> | React.SetStateAction<ProductionProcessListPersistedState> | ||||
| @@ -93,6 +96,8 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({ | |||||
| printerCombo, | printerCombo, | ||||
| onSelectMatchingStock, | onSelectMatchingStock, | ||||
| qcReady, | qcReady, | ||||
| includePutaway, | |||||
| putawayStatus, | |||||
| listPersistedState, | listPersistedState, | ||||
| onListPersistedStateChange, | onListPersistedStateChange, | ||||
| }) => { | }) => { | ||||
| @@ -258,7 +263,8 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({ | |||||
| itemCode: appliedSearch.itemCode, | itemCode: appliedSearch.itemCode, | ||||
| jobOrderCode: appliedSearch.jobOrderCode, | jobOrderCode: appliedSearch.jobOrderCode, | ||||
| qcReady, | qcReady, | ||||
| includePutaway: qcReady ? true : null, | |||||
| includePutaway: includePutaway ?? (qcReady ? true : null), | |||||
| putawayStatus, | |||||
| type: typeParam, | type: typeParam, | ||||
| page, | page, | ||||
| size: PAGE_SIZE, | size: PAGE_SIZE, | ||||
| @@ -273,7 +279,7 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({ | |||||
| } finally { | } finally { | ||||
| setLoading(false); | setLoading(false); | ||||
| } | } | ||||
| }, [listPersistedState, qcReady]); | |||||
| }, [listPersistedState, qcReady, includePutaway, putawayStatus]); | |||||
| useEffect(() => { | useEffect(() => { | ||||
| fetchProcesses(); | fetchProcesses(); | ||||
| @@ -36,7 +36,10 @@ const ProductionProcessPage: React.FC<ProductionProcessPageProps> = ({ printerCo | |||||
| const [productionListState, setProductionListState] = useState( | const [productionListState, setProductionListState] = useState( | ||||
| createDefaultProductionProcessListPersistedState, | createDefaultProductionProcessListPersistedState, | ||||
| ); | ); | ||||
| const [finishedQcListState, setFinishedQcListState] = useState( | |||||
| const [waitingPutawayListState, setWaitingPutawayListState] = useState( | |||||
| createDefaultProductionProcessListPersistedState, | |||||
| ); | |||||
| const [putawayedListState, setPutawayedListState] = useState( | |||||
| createDefaultProductionProcessListPersistedState, | createDefaultProductionProcessListPersistedState, | ||||
| ); | ); | ||||
| const { data: session } = useSession() as { data: SessionWithTokens | null }; | const { data: session } = useSession() as { data: SessionWithTokens | null }; | ||||
| @@ -199,7 +202,8 @@ const ProductionProcessPage: React.FC<ProductionProcessPageProps> = ({ printerCo | |||||
| <Tabs value={tabIndex} onChange={handleTabChange} sx={{ mb: 2 }}> | <Tabs value={tabIndex} onChange={handleTabChange} sx={{ mb: 2 }}> | ||||
| <Tab label={t("Production Process")} /> | <Tab label={t("Production Process")} /> | ||||
| <Tab label={t("Finished QC Job Orders")} /> | |||||
| <Tab label={t("Waiting QC Put Away Job Orders")} /> | |||||
| <Tab label={t("Put Awayed Job Orders")} /> | |||||
| <Tab label={t("Job Process Status Dashboard")} /> | <Tab label={t("Job Process Status Dashboard")} /> | ||||
| <Tab label={t("Operator KPI Dashboard")} /> | <Tab label={t("Operator KPI Dashboard")} /> | ||||
| <Tab label={t("Production Equipment Status Dashboard")} /> | <Tab label={t("Production Equipment Status Dashboard")} /> | ||||
| @@ -231,8 +235,10 @@ const ProductionProcessPage: React.FC<ProductionProcessPageProps> = ({ printerCo | |||||
| <ProductionProcessList | <ProductionProcessList | ||||
| printerCombo={printerCombo} | printerCombo={printerCombo} | ||||
| qcReady={true} | qcReady={true} | ||||
| listPersistedState={finishedQcListState} | |||||
| onListPersistedStateChange={setFinishedQcListState} | |||||
| includePutaway={true} | |||||
| putawayStatus="notCompleted" | |||||
| listPersistedState={waitingPutawayListState} | |||||
| onListPersistedStateChange={setWaitingPutawayListState} | |||||
| onSelectProcess={(jobOrderId) => { | onSelectProcess={(jobOrderId) => { | ||||
| const id = jobOrderId ?? null; | const id = jobOrderId ?? null; | ||||
| if (id !== null) { | if (id !== null) { | ||||
| @@ -248,13 +254,36 @@ const ProductionProcessPage: React.FC<ProductionProcessPageProps> = ({ printerCo | |||||
| }} | }} | ||||
| /> | /> | ||||
| )} | )} | ||||
| {tabIndex === 2 && ( | |||||
| <JobProcessStatus /> | |||||
| {tabIndex === 2 && ( | |||||
| <ProductionProcessList | |||||
| printerCombo={printerCombo} | |||||
| qcReady={true} | |||||
| includePutaway={true} | |||||
| putawayStatus="completed" | |||||
| listPersistedState={putawayedListState} | |||||
| onListPersistedStateChange={setPutawayedListState} | |||||
| onSelectProcess={(jobOrderId) => { | |||||
| const id = jobOrderId ?? null; | |||||
| if (id !== null) { | |||||
| setSelectedProcessId(id); | |||||
| } | |||||
| }} | |||||
| onSelectMatchingStock={(jobOrderId, productProcessId, pickOrderId) => { | |||||
| setSelectedMatchingStock({ | |||||
| jobOrderId: jobOrderId || 0, | |||||
| productProcessId: productProcessId || 0, | |||||
| pickOrderId: pickOrderId || 0, | |||||
| }); | |||||
| }} | |||||
| /> | |||||
| )} | )} | ||||
| {tabIndex === 3 && ( | {tabIndex === 3 && ( | ||||
| <OperatorKpiDashboard /> | |||||
| <JobProcessStatus /> | |||||
| )} | )} | ||||
| {tabIndex === 4 && ( | {tabIndex === 4 && ( | ||||
| <OperatorKpiDashboard /> | |||||
| )} | |||||
| {tabIndex === 5 && ( | |||||
| <EquipmentStatusDashboard /> | <EquipmentStatusDashboard /> | ||||
| )} | )} | ||||
| </Box> | </Box> | ||||
| @@ -128,6 +128,7 @@ const PutAwayModal: React.FC<Props> = ({ open, onClose, warehouse, stockInLineId | |||||
| ...defaultNewValue, | ...defaultNewValue, | ||||
| }, | }, | ||||
| }); | }); | ||||
| const { isSubmitting } = formProps.formState; | |||||
| const errors = formProps.formState.errors; | const errors = formProps.formState.errors; | ||||
| useEffect(() => { | useEffect(() => { | ||||
| @@ -646,7 +647,7 @@ const PutAwayModal: React.FC<Props> = ({ open, onClose, warehouse, stockInLineId | |||||
| }, | }, | ||||
| }} | }} | ||||
| // onClick={formProps.handleSubmit()} | // onClick={formProps.handleSubmit()} | ||||
| disabled={!verified || qtyError != ""} | |||||
| disabled={!verified || qtyError != "" || isSubmitting} | |||||
| > | > | ||||
| {t("confirm putaway")} | {t("confirm putaway")} | ||||
| </Button> | </Button> | ||||
| @@ -12,6 +12,8 @@ | |||||
| "Please Select BOM": "請選擇 BOM", | "Please Select BOM": "請選擇 BOM", | ||||
| "No Lot": "沒有批號", | "No Lot": "沒有批號", | ||||
| "Select All": "全選", | "Select All": "全選", | ||||
| "Waiting QC Put Away Job Orders": "待QC上架工單", | |||||
| "Put Awayed Job Orders": "已上架工單", | |||||
| "Loading BOM Detail...": "正在載入 BOM 明細…", | "Loading BOM Detail...": "正在載入 BOM 明細…", | ||||
| "Output Quantity": "使用數量", | "Output Quantity": "使用數量", | ||||
| "Process & Equipment": "製程與設備", | "Process & Equipment": "製程與設備", | ||||