Browse Source

update

master
CANCERYS\kw093 3 weeks ago
parent
commit
32f7897ca8
8 changed files with 1009 additions and 342 deletions
  1. +3
    -2
      src/app/(main)/production/page.tsx
  2. +111
    -45
      src/app/api/jo/actions.ts
  3. +2
    -5
      src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx
  4. +61
    -19
      src/components/ProductionProcess/ProductionProcessDetail.tsx
  5. +411
    -0
      src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx
  6. +40
    -5
      src/components/ProductionProcess/ProductionProcessList.tsx
  7. +22
    -38
      src/components/ProductionProcess/ProductionProcessPage.tsx
  8. +359
    -228
      src/components/ProductionProcess/ProductionProcessStepExecution.tsx

+ 3
- 2
src/app/(main)/production/page.tsx View File

@@ -8,6 +8,7 @@ import Typography from "@mui/material/Typography";
import { Metadata } from "next";
import Link from "next/link";
import { Suspense } from "react";
import { fetchPrinterCombo } from "@/app/api/settings/printer";

export const metadata: Metadata = {
title: "Claims",
@@ -15,7 +16,7 @@ export const metadata: Metadata = {

const production: React.FC = async () => {
const { t } = await getServerI18n("claims");
const printerCombo = await fetchPrinterCombo();
return (
<>
<Stack
@@ -37,7 +38,7 @@ const production: React.FC = async () => {
{t("Create Process")}
</Button> */}
</Stack>
<ProductionProcessPage /> {/* Use new component */}
<ProductionProcessPage printerCombo={printerCombo} /> {/* Use new component */}
</>
);
};


+ 111
- 45
src/app/api/jo/actions.ts View File

@@ -171,20 +171,31 @@ export interface ProductProcessResponse {
}

export interface ProductProcessLineResponse {
id: number;
seqNo: number;
name: string;
description?: string;
equipmentType?: string;
startTime?: string;
endTime?: string;
outputFromProcessQty?: number;
outputFromProcessUom?: string;
defectQty?: number;
scrapQty?: number;
byproductName?: string;
byproductQty?: number;
handlerId?: number;
id: number,
bomprocessId: number,
operatorId: number,
operatorName: string,
equipmentId: number,
handlerId: number,
seqNo: number,
name: string,
description: string,
equipment_name: string,
status: string,
byproductId: number,
byproductName: string,
byproductQty: number,
byproductUom: string,
scrapQty: number,
defectQty: number,
defectUom: string,
outputFromProcessQty: number,
outputFromProcessUom: string,
durationInMinutes: number,
prepTimeInMinutes: number,
postProdTimeInMinutes: number,
startTime: string,
endTime: string,
}

export interface ProductProcessWithLinesResponse {
@@ -196,7 +207,19 @@ export interface ProductProcessWithLinesResponse {
date: string;
bomId?: number;
jobOrderId?: number;
jobOrderCode: string;
isDark: string;
isDense: number;
isFloat: string;
itemId: number;
itemCode: string;
itemName: string;
outputQty: number;
outputQtyUom: string;
productionPriority: number;
jobOrderLines: JobOrderLineInfo[];
productProcessLines: ProductProcessLineResponse[];
}
export interface UpdateProductProcessLineQtyRequest {
productProcessLineId: number;
@@ -241,6 +264,7 @@ export interface AllJoborderProductProcessInfoResponse {
bomId?: number;
itemName: string;
jobOrderId: number;
stockInLineId: number;
jobOrderCode: string;
productProcessLineCount: number;
FinishedProductProcessLineCount: number;
@@ -261,6 +285,7 @@ export interface ProductProcessLineQrscanUpadteRequest {
operatorId?: number;
equipmentId?: number;
}

export interface ProductProcessLineDetailResponse {
id: number,
productProcessId: number,
@@ -270,9 +295,17 @@ export interface ProductProcessLineDetailResponse {
operatorName: string,
handlerId: number,
seqNo: number,
isDark: string,
isDense: number,
isFloat: string,
outputQtyUom: string,
outputQty: number,
pickOrderId: number,
jobOrderCode: string,
jobOrderId: number,
name: string,
description: string,
equipmentId: number,
equipment: string,
startTime: string,
endTime: string,
defectQty: number,
@@ -283,50 +316,83 @@ export interface ProductProcessLineDetailResponse {
byproductName: string,
byproductQty: number,
byproductUom: string | undefined,
totalStockQty: number,
insufficientStockQty: number,
sufficientStockQty: number,
productionPriority: number,
productProcessLines: ProductProcessLineInfoResponse[],
jobOrderLineInfo: JobOrderLineInfo[],
}
export interface ProductProcessLineDetailResponse {
export interface JobOrderProcessLineDetailResponse {
id: number;
productProcessId: number;
bomProcessId: number;
operatorId: number;
equipmentType: string | null;
operatorName: string;
handlerId: number;
seqNo: number;
name: string;
description: string;
equipmentId: number;
startTime: string | number[]; // API 返回的是数组格式
endTime: string | number[]; // API 返回的是数组格式
status: string;
outputFromProcessQty: number;
outputFromProcessUom: string;
defectQty: number;
defectUom: string;
scrapQty: number;
scrapUom: string;
byproductId: number;
byproductName: string;
byproductQty: number;
byproductUom: string;
}
export interface JobOrderLineInfo {
id: number,
productProcessId: number,
bomProcessId: number,
jobOrderId: number,
jobOrderCode: string,
itemId: number,
itemCode: string,
itemName: string,
reqQty: number,
stockQty: number,
uom: string,
shortUom: string,
availableStatus: string
}
export interface ProductProcessLineInfoResponse {
id: number,
bomprocessId: number,
operatorId: number,
equipmentType: string,
operatorName: string,
equipmentId: number,
handlerId: number,
seqNo: number,
isDark: string,
isDense: number,
isFloat: string,
outputQtyUom: string,
outputQty: number,
pickOrderId: number,
jobOrderCode: string,
jobOrderId: number,
name: string,
description: string,
equipment: string,
startTime: string,
endTime: string,
defectQty: number,
defectUom: string,
scrapQty: number,
scrapUom: string,
equipment_name: string,
status: string,
byproductId: number,
byproductName: string,
byproductQty: number,
byproductUom: string | undefined,
byproductUom: string,
scrapQty: number,
defectQty: number,
defectUom: string,
durationInMinutes: number,
prepTimeInMinutes: number,
postProdTimeInMinutes: number,
outputFromProcessQty: number,
outputFromProcessUom: string,
startTime: string,
endTime: string
}
export const fetchProductProcessLinesByJoid = cache(async (joid: number) => {
return serverFetchJson<ProductProcessLineDetailResponse>(
`${BASE_API_URL}/product-process/demo/joid/${joid}`,
{
method: "GET",
}
);
});

// /product-process/Demo/ProcessLine/detail/{lineId}

export const fetchProductProcessLineDetail = cache(async (lineId: number) => {
return serverFetchJson<ProductProcessLineDetailResponse>(
return serverFetchJson<JobOrderProcessLineDetailResponse>(
`${BASE_API_URL}/product-process/Demo/ProcessLine/detail/${lineId}`,
{
method: "GET",


+ 2
- 5
src/components/FinishedGoodSearch/GoodPickExecutionForm.tsx View File

@@ -189,12 +189,9 @@ const validateForm = (): boolean => {
newErrors.actualPickQty = t('Qty cannot be negative');
}
// 2. 检查 actualPickQty 不能超过可用数量或需求数量
<<<<<<< Updated upstream
if (ap > Math.min(req)) {
=======


if (ap > Math.min( req)) {
>>>>>>> Stashed changes
newErrors.actualPickQty = t('Qty is not allowed to be greater than required/available qty');
}


+ 61
- 19
src/components/ProductionProcess/ProductionProcessDetail.tsx View File

@@ -34,11 +34,12 @@ import dayjs from "dayjs";
import { OUTPUT_DATE_FORMAT } from "@/app/utils/formatUtil";
import {
fetchProductProcessById,
fetchProductProcessLines,
updateProductProcessLineQrscan,
fetchProductProcessLineDetail,
ProductProcessLineDetailResponse,
JobOrderProcessLineDetailResponse,
updateLineOutput,
ProductProcessLineInfoResponse,
ProductProcessResponse,
ProductProcessLineResponse,
completeProductProcessLine,
@@ -47,14 +48,6 @@ import {
} from "@/app/api/jo/actions";
import { fetchNameList, NameList } from "@/app/api/user/actions";
import ProductionProcessStepExecution from "./ProductionProcessStepExecution";
// 添加设备数据库(从 MachineScanner.tsx)
const machineDatabase: Machine[] = [
{ id: 1, name: "CNC Mill #1", code: "CNC001", qrCode: "QR-CNC001" },
{ id: 2, name: "Lathe #2", code: "LAT002", qrCode: "QR-LAT002" },
{ id: 3, name: "Press #3", code: "PRS003", qrCode: "QR-PRS003" },
{ id: 4, name: "Welder #4", code: "WLD004", qrCode: "QR-WLD004" },
{ id: 5, name: "Drill Press #5", code: "DRL005", qrCode: "QR-DRL005" },
];


interface ProductProcessDetailProps {
@@ -73,7 +66,7 @@ const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({
// 基本信息
const [processData, setProcessData] = useState<any>(null);
const [lines, setLines] = useState<ProductProcessLineDetailResponse[]>([]);
const [lines, setLines] = useState<ProductProcessLineInfoResponse[]>([]);
const [loading, setLoading] = useState(false);
// 选中的 line 和执行状态
@@ -86,7 +79,7 @@ const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({
const [scannedOperatorId, setScannedOperatorId] = useState<number | null>(null);
const [scannedEquipmentId, setScannedEquipmentId] = useState<number | null>(null);
const [scanningLineId, setScanningLineId] = useState<number | null>(null);
const [lineDetailForScan, setLineDetailForScan] = useState<ProductProcessLineDetailResponse | null>(null);
const [lineDetailForScan, setLineDetailForScan] = useState<JobOrderProcessLineDetailResponse | null>(null);
const [showScanDialog, setShowScanDialog] = useState(false);
const autoSubmitTimerRef = useRef<NodeJS.Timeout | null>(null);

@@ -105,7 +98,11 @@ const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({

// 处理 QR 码扫描
// 处理 QR 码扫描
const handleBackFromStep = async () => {
await fetchProcessDetail(); // 重新拉取最新的 process/lines
setIsExecutingLine(false);
setSelectedLineId(null);
};

// 获取 process 和 lines 数据
const fetchProcessDetail = useCallback(async () => {
@@ -230,6 +227,7 @@ const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({

if (!scannedOperatorId) {
console.log("No operatorId, cannot submit");
setIsAutoSubmitting(false);
return false; // 没有 operatorId,不能提交
}

@@ -255,8 +253,15 @@ const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({
// 检查响应中的 message 字段来判断是否成功
// 如果后端返回 message 不为 null,说明验证失败
if (response && response.message) {
setIsAutoSubmitting(false);
// 清除定时器
if (autoSubmitTimerRef.current) {
clearTimeout(autoSubmitTimerRef.current);
autoSubmitTimerRef.current = null;
}
//alert(response.message || t("Validation failed. Please check operator and equipment."));
return false;
}
// 验证通过,继续执行后续步骤
console.log("Validation passed, starting line...");
@@ -297,11 +302,17 @@ const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({

// 开始扫描
const handleStartScan = useCallback((lineId: number) => {
if (autoSubmitTimerRef.current) {
clearTimeout(autoSubmitTimerRef.current);
autoSubmitTimerRef.current = null;
}
setScanningLineId(lineId);
setIsManualScanning(true);
setProcessedQrCodes(new Set());
setScannedOperatorId(null);
setScannedEquipmentId(null);
setIsAutoSubmitting(false); // 添加:重置自动提交状态
setLineDetailForScan(null);
// 获取 line detail 以获取 bomProcessEquipmentId
fetchProductProcessLineDetail(lineId)
.then(setLineDetailForScan)
@@ -311,7 +322,16 @@ const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({

// 停止扫描
const handleStopScan = useCallback(() => {
console.log("🛑 Stopping scan");
// 清除定时器
if (autoSubmitTimerRef.current) {
clearTimeout(autoSubmitTimerRef.current);
autoSubmitTimerRef.current = null;
}
setIsManualScanning(false);
setIsAutoSubmitting(false); // 添加:重置自动提交状态
stopScan();
resetScan();
}, [stopScan, resetScan]);
@@ -374,11 +394,24 @@ const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({
}, []);

const handleStartLineWithScan = async (lineId: number) => {
console.log("🚀 Starting line with scan for lineId:", lineId);
// 确保状态完全重置
setIsAutoSubmitting(false);
setScannedOperatorId(null);
setScannedEquipmentId(null);
setProcessedQrCodes(new Set());
// 清除之前的定时器
if (autoSubmitTimerRef.current) {
clearTimeout(autoSubmitTimerRef.current);
autoSubmitTimerRef.current = null;
}
setScanningLineId(lineId);
setShowScanDialog(true);
handleStartScan(lineId);
};

const selectedLine = lines.find(l => l.id === selectedLineId);

if (loading) {
@@ -391,14 +424,14 @@ const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({

return (
<Box>
{/* 返回按钮 */}
<Box sx={{ mb: 2 }}>
{/*
<Box sx={{ mb: 2 }}>
<Button variant="outlined" onClick={onBack}>
{t("Back to List")}
</Button>
</Box>

{/* ========== 第一部分:基本信息 ========== */}
<Paper sx={{ p: 3, mb: 3 }}>
<Typography variant="h6" gutterBottom fontWeight="bold">
{t("Production Process Information")}
@@ -437,7 +470,7 @@ const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({
</Typography>
</Stack>
</Paper>
*/}
{/* ========== 第二部分:Process Lines ========== */}
<Paper sx={{ p: 3 }}>
<Typography variant="h6" gutterBottom fontWeight="bold">
@@ -452,8 +485,12 @@ const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({
<TableRow>
<TableCell>{t("Seq")}</TableCell>
<TableCell>{t("Step Name")}</TableCell>
<TableCell>{t("Description")}</TableCell>
<TableCell>{t("Description")}</TableCell>
<TableCell>{t("Operator")}</TableCell>
<TableCell>{t("Equipment Type")}</TableCell>
<TableCell>{t("Duration")}</TableCell>
<TableCell>{t("Prep Time")}</TableCell>
<TableCell>{t("Post Prod Time")}</TableCell>
<TableCell align="center">{t("Status")}</TableCell>
<TableCell align="center">{t("Action")}</TableCell>
</TableRow>
@@ -462,7 +499,7 @@ const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({
{lines.map((line) => {
const status = (line as any).status || '';
const statusLower = status.toLowerCase();
const equipmentName = (line as any).equipment_name || line.equipmentType || "-";
const equipmentName = line.equipment_name || "-";
const isCompleted = statusLower === 'completed';
const isInProgress = statusLower === 'inprogress' || statusLower === 'in progress';
@@ -475,7 +512,11 @@ const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({
<Typography fontWeight={500}>{line.name}</Typography>
</TableCell>
<TableCell><Typography fontWeight={500}>{line.description || "-"}</Typography></TableCell>
<TableCell><Typography fontWeight={500}>{line.operatorName}</Typography></TableCell>
<TableCell><Typography fontWeight={500}>{equipmentName}</Typography></TableCell>
<TableCell><Typography fontWeight={500}>{line.durationInMinutes} {t("Minutes")}</Typography></TableCell>
<TableCell><Typography fontWeight={500}>{line.prepTimeInMinutes} {t("Minutes")}</Typography></TableCell>
<TableCell><Typography fontWeight={500}>{line.postProdTimeInMinutes} {t("Minutes")}</Typography></TableCell>
<TableCell align="center">
{isCompleted ? (
<Chip label={t("Completed")} color="success" size="small" />
@@ -534,6 +575,7 @@ const ProductionProcessDetail: React.FC<ProductProcessDetailProps> = ({
/* ========== 步骤执行视图 ========== */
<ProductionProcessStepExecution
lineId={selectedLineId}
onBack={handleBackFromStep}
//onClose={() => {
// setIsExecutingLine(false)
// setSelectedLineId(null)


+ 411
- 0
src/components/ProductionProcess/ProductionProcessJobOrderDetail.tsx View File

@@ -0,0 +1,411 @@
"use client";
import React, { useCallback, useEffect, useState } from "react";
import {
Box,
Button,
Paper,
Stack,
Typography,
TextField,
Grid,
Card,
CardContent,
CircularProgress,
Tabs,
Tab,
TabsProps,
} from "@mui/material";
import ArrowBackIcon from '@mui/icons-material/ArrowBack';
import { useTranslation } from "react-i18next";
import { fetchProductProcessesByJobOrderId } from "@/app/api/jo/actions";
import ProductionProcessDetail from "./ProductionProcessDetail";
import dayjs from "dayjs";
import { OUTPUT_DATE_FORMAT, integerFormatter, arrayToDateString } from "@/app/utils/formatUtil";
import StyledDataGrid from "../StyledDataGrid/StyledDataGrid";
import { GridColDef, GridRenderCellParams } from "@mui/x-data-grid";
import { decimalFormatter } from "@/app/utils/formatUtil";
import CheckCircleOutlineOutlinedIcon from '@mui/icons-material/CheckCircleOutlineOutlined';
import DoDisturbAltRoundedIcon from '@mui/icons-material/DoDisturbAltRounded';
import { fetchInventories } from "@/app/api/inventory/actions";
import { InventoryResult } from "@/app/api/inventory";
import JobPickExecutionsecondscan from "../Jodetail/JobPickExecutionsecondscan";
interface JobOrderLine {
id: number;
jobOrderId: number;
jobOrderCode: string;
itemId: number;
itemCode: string;
itemName: string;
reqQty: number;
stockQty: number;
uom: string;
shortUom: string;
availableStatus: string;
}

interface ProductProcessJobOrderDetailProps {
jobOrderId: number;
onBack: () => void;
}

const ProductionProcessJobOrderDetail: React.FC<ProductProcessJobOrderDetailProps> = ({
jobOrderId,
onBack,
}) => {
const { t } = useTranslation();
const [loading, setLoading] = useState(false);
const [processData, setProcessData] = useState<any>(null);
const [jobOrderLines, setJobOrderLines] = useState<JobOrderLine[]>([]);
const [inventoryData, setInventoryData] = useState<InventoryResult[]>([]);
const [tabIndex, setTabIndex] = useState(0);
const [selectedProcessId, setSelectedProcessId] = useState<number | null>(null);

// 获取数据
const fetchData = useCallback(async () => {
setLoading(true);
try {
const data = await fetchProductProcessesByJobOrderId(jobOrderId);
if (data && data.length > 0) {
const firstProcess = data[0];
setProcessData(firstProcess);
setJobOrderLines((firstProcess as any).jobOrderLines || []);
}
} catch (error) {
console.error("Error loading data:", error);
} finally {
setLoading(false);
}
}, [jobOrderId]);

// 获取库存数据
useEffect(() => {
const fetchInventoryData = async () => {
try {
const inventoryResponse = await fetchInventories({
code: "",
name: "",
type: "",
pageNum: 0,
pageSize: 1000
});
setInventoryData(inventoryResponse.records);
} catch (error) {
console.error("Error fetching inventory data:", error);
}
};
fetchInventoryData();
}, []);

useEffect(() => {
fetchData();
}, [fetchData]);

const handleTabChange = useCallback<NonNullable<TabsProps["onChange"]>>(
(_e, newValue) => {
setTabIndex(newValue);
},
[],
);

// 如果选择了 process detail,显示 detail 页面
if (selectedProcessId !== null) {
return (
<ProductionProcessDetail
jobOrderId={selectedProcessId}
onBack={() => {
setSelectedProcessId(null);
fetchData(); // 刷新数据
}}
/>
);
}

if (loading) {
return (
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
<CircularProgress/>
</Box>
);
}

if (!processData) {
return (
<Box>
<Button variant="outlined" onClick={onBack} startIcon={<ArrowBackIcon />}>
{t("Back")}
</Button>
<Typography sx={{ mt: 2 }}>{t("No data found")}</Typography>
</Box>
);
}
// InfoCard 组件内容
const InfoCardContent = () => (
<Card sx={{ display: "block", mt: 2 }}>
<CardContent component={Stack} spacing={4}>
<Box>
<Grid container spacing={2} columns={{ xs: 6, sm: 12 }}>
<Grid item xs={6}>
<TextField
label={t("Job Order Code")}
fullWidth
disabled={true}
value={processData?.jobOrderCode || ""}
/>
</Grid>
<Grid item xs={6}/>
<Grid item xs={6}>
<TextField
label={t("Item Code")}
fullWidth
disabled={true}
value={processData?.itemCode || ""}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Item Name")}
fullWidth
disabled={true}
value={processData?.itemName || ""}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Req. Qty")}
fullWidth
disabled={true}
value={processData?.outputQty ? integerFormatter.format(processData.outputQty) : ""}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("UoM")}
fullWidth
disabled={true}
value={processData?.outputQtyUom || ""}
/>
</Grid>
<Grid item xs={6}>
<TextField
value={processData?.date ? dayjs(processData.date).format(OUTPUT_DATE_FORMAT) : ""}
label={t("Target Production Date")}
fullWidth
disabled={true}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Production Priority")}
fullWidth
disabled={true}
value={processData?.productionPriority || "0"}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Is Dark")}
fullWidth
disabled={true}
value={processData?.isDark || ""}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Is Dense")}
fullWidth
disabled={true}
value={processData?.isDense || ""}
/>
</Grid>
<Grid item xs={6}>
<TextField
label={t("Is Float")}
fullWidth
disabled={true}
value={processData?.isFloat || ""}
/>
</Grid>
</Grid>
</Box>
</CardContent>
</Card>
);

// PickTable 组件内容
const getStockAvailable = (line: JobOrderLine) => {
const inventory = inventoryData.find(inv =>
inv.itemCode === line.itemCode || inv.itemName === line.itemName
);
if (inventory) {
return inventory.availableQty || (inventory.onHandQty - inventory.onHoldQty - inventory.unavailableQty);
}
return line.stockQty || 0;
};

const isStockSufficient = (line: JobOrderLine) => {
const stockAvailable = getStockAvailable(line);
return stockAvailable >= line.reqQty;
};
const productionProcessesLineRemarkTableColumns: GridColDef[] = [
{
field: "seqNo",
headerName: t("Seq"),
flex: 0.2,
align: "left",
headerAlign: "center",
type: "number",
},
{
field: "description",
headerName: t("Remark"),
flex: 1,
align: "left",
headerAlign: "center",
},
];
const productionProcessesLineRemarkTableRows =
processData?.productProcessLines?.map((line: any) => ({
id: line.seqNo,
seqNo: line.seqNo,
description: line.description ?? "",
})) ?? [];




const pickTableColumns: GridColDef[] = [
{
field: "id",
headerName: t("id"),
flex: 0.2,
align: "left",
headerAlign: "left",
type: "number",
},
{
field: "itemCode",
headerName: t("Item Code"),
flex: 0.6,
},
{
field: "itemName",
headerName: t("Item Name"),
flex: 1,
renderCell: (params: GridRenderCellParams<JobOrderLine>) => {
return `${params.value} (${params.row.uom})`;
},
},
{
field: "reqQty",
headerName: t("Req. Qty"),
flex: 0.7,
align: "right",
headerAlign: "right",
renderCell: (params: GridRenderCellParams<JobOrderLine>) => {
return `${decimalFormatter.format(params.value)} (${params.row.shortUom})`;
},
},
{
field: "stockAvailable",
headerName: t("Stock Available"),
flex: 0.7,
align: "right",
headerAlign: "right",
type: "number",

renderCell: (params: GridRenderCellParams<JobOrderLine>) => {
const stockAvailable = getStockAvailable(params.row);
return `${decimalFormatter.format(stockAvailable)} (${params.row.shortUom})`;
},
},
{
field: "stockStatus",
headerName: t("Stock Status"),
flex: 0.5,
align: "right",
headerAlign: "right",
type: "boolean",
renderCell: (params: GridRenderCellParams<JobOrderLine>) => {
return isStockSufficient(params.row)
? <CheckCircleOutlineOutlinedIcon fontSize={"large"} color="success" />
: <DoDisturbAltRoundedIcon fontSize={"large"} color="error" />;
},
},
];

const pickTableRows = jobOrderLines.map((line, index) => ({
...line,
id: line.id || index,
}));

const PickTableContent = () => (
<Box sx={{ mt: 2 }}>
<StyledDataGrid
sx={{
"--DataGrid-overlayHeight": "100px",
}}
disableColumnMenu
rows={pickTableRows}
columns={pickTableColumns}
getRowHeight={() => 'auto'}
/>
</Box>
);
const ProductionProcessesLineRemarkTableContent = () => (
<Box sx={{ mt: 2 }}>
<StyledDataGrid
sx={{
"--DataGrid-overlayHeight": "100px",
}}
disableColumnMenu
rows={productionProcessesLineRemarkTableRows ?? []}
columns={productionProcessesLineRemarkTableColumns}
getRowHeight={() => 'auto'}
/>
</Box>
);

return (
<Box>
{/* 返回按钮 */}
<Box sx={{ mb: 2 }}>
<Button variant="outlined" onClick={onBack} startIcon={<ArrowBackIcon />}>
{t("Back to List")}
</Button>
</Box>

{/* 标签页 */}
<Box sx={{ borderBottom: '1px solid #e0e0e0' }}>
<Tabs value={tabIndex} onChange={handleTabChange} variant="scrollable">
<Tab label={t("Job Order Info")} />
<Tab label={t("Job Order Lines")} />
<Tab label={t("Production Process")} />
<Tab label={t("Production Process Line Remark")} />
<Tab label={t("Matching Stock")} />
</Tabs>
</Box>

{/* 标签页内容 */}
<Box sx={{ p: 2 }}>
{tabIndex === 0 && <InfoCardContent />}
{tabIndex === 1 && <PickTableContent />}
{tabIndex === 2 && (
<ProductionProcessDetail
jobOrderId={jobOrderId}
onBack={() => {
// 切换回第一个标签页,或者什么都不做
setTabIndex(0);
}}
/>
)}
{tabIndex === 3 && <ProductionProcessesLineRemarkTableContent />}
{tabIndex === 4 && <JobPickExecutionsecondscan filterArgs={{ jobOrderId: jobOrderId }} />}
</Box>
</Box>
);
};

export default ProductionProcessJobOrderDetail;

+ 40
- 5
src/components/ProductionProcess/ProductionProcessList.tsx View File

@@ -14,6 +14,7 @@ import {
Grid,
} from "@mui/material";
import { useTranslation } from "react-i18next";
import QcStockInModal from "../Qc/QcStockInModal";
import { useSession } from "next-auth/react";
import { SessionWithTokens } from "@/config/authConfig";
import dayjs from "dayjs";
@@ -22,21 +23,38 @@ import {
fetchAllJoborderProductProcessInfo,
AllJoborderProductProcessInfoResponse,
} from "@/app/api/jo/actions";

import { StockInLineInput } from "@/app/api/stockIn";
import { PrinterCombo } from "@/app/api/settings/printer";
interface ProductProcessListProps {
onSelectProcess: (jobOrderId: number|undefined, productProcessId: number|undefined) => void;
printerCombo: PrinterCombo[];
}


const PER_PAGE = 6;

const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess }) => {
const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess, printerCombo }) => {
const { t } = useTranslation();
const { data: session } = useSession() as { data: SessionWithTokens | null };
const sessionToken = session as SessionWithTokens | null;
const [loading, setLoading] = useState(false);
const [processes, setProcesses] = useState<AllJoborderProductProcessInfoResponse[]>([]);
const [page, setPage] = useState(0);
const [openModal, setOpenModal] = useState<boolean>(false);
const [modalInfo, setModalInfo] = useState<StockInLineInput>();
const handleViewStockIn = useCallback((process: AllJoborderProductProcessInfoResponse) => {
if (!process.stockInLineId) {
alert(t("Invalid Stock In Line Id"));
return;
}

setModalInfo({
id: process.stockInLineId,
expiryDate: dayjs().add(1, "month").format(OUTPUT_DATE_FORMAT),
// 视需要补 itemId、jobOrderId 等
});
setOpenModal(true);
}, [t]);
const fetchProcesses = useCallback(async () => {
setLoading(true);
try {
@@ -54,7 +72,12 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess
useEffect(() => {
fetchProcesses();
}, [fetchProcesses]);

const closeNewModal = useCallback(() => {
// const response = updateJo({ id: 1, status: "storing" });
setOpenModal(false); // Close the modal first
// setTimeout(() => {
// }, 300); // Add a delay to avoid immediate re-trigger of useEffect
}, []);
const startIdx = page * PER_PAGE;
const paged = processes.slice(startIdx, startIdx + PER_PAGE);

@@ -155,6 +178,11 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess
<Button variant="contained" size="small" onClick={() => onSelectProcess(process.jobOrderId, process.id)}>
{t("View Details")}
</Button>
{statusLower === "completed" && (
<Button onClick={() => handleViewStockIn(process)}>
{t("view stockin")}
</Button>
)}
<Box sx={{ flex: 1 }} />
<Typography variant="caption" color="text.secondary">
{t("Lines")}: {totalCount}
@@ -165,7 +193,13 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess
);
})}
</Grid>

<QcStockInModal
session={sessionToken}
open={openModal}
onClose={closeNewModal}
inputDetail={modalInfo}
printerCombo={printerCombo}
/>
{processes.length > 0 && (
<TablePagination
component="div"
@@ -179,6 +213,7 @@ const ProductProcessList: React.FC<ProductProcessListProps> = ({ onSelectProcess
</Box>
)}
</Box>
);
};


+ 22
- 38
src/components/ProductionProcess/ProductionProcessPage.tsx View File

@@ -4,53 +4,38 @@ import { useSession } from "next-auth/react";
import { SessionWithTokens } from "@/config/authConfig";
import ProductionProcessList from "@/components/ProductionProcess/ProductionProcessList";
import ProductionProcessDetail from "@/components/ProductionProcess/ProductionProcessDetail";
import ProductionProcessJobOrderDetail from "@/components/ProductionProcess/ProductionProcessJobOrderDetail";

import {
fetchProductProcesses,
fetchProductProcessLines,
fetchProductProcessesByJobOrderId,
ProductProcessLineResponse
} from "@/app/api/jo/actions";
type PrinterCombo = {
id: number;
value: number;
label?: string;
code?: string;
name?: string;
description?: string;
ip?: string;
port?: number;
};

interface ProductionProcessPageProps {
printerCombo: PrinterCombo[];
}

const ProductionProcessPage = () => {
const ProductionProcessPage: React.FC<ProductionProcessPageProps> = ({ printerCombo }) => {
const [selectedProcessId, setSelectedProcessId] = useState<number | null>(null);
const { data: session } = useSession() as { data: SessionWithTokens | null };
const currentUserId = session?.id ? parseInt(session.id) : undefined;

const checkAndRedirectToDetail = useCallback(async () => {
if (!currentUserId) return;
try {
// 获取所有 processes
const processes = await fetchProductProcesses();
// 获取所有 lines 并检查是否有匹配的
for (const process of processes.content || []) {
const lines = await fetchProductProcessLines(process.id);
const pendingLine = lines.find((line: ProductProcessLineResponse) =>
line.handlerId === currentUserId &&
!line.endTime &&
line.startTime
);
if (pendingLine) {
setSelectedProcessId(process.id);
break;
}
}
} catch (error) {
console.error("Error checking pending lines:", error);
}
}, [currentUserId]);

useEffect(() => {
if (currentUserId && !selectedProcessId) {
// 检查是否有当前用户的 pending line
checkAndRedirectToDetail();
}
}, [currentUserId, selectedProcessId, checkAndRedirectToDetail]);
// …原有逻辑省略…

if (selectedProcessId !== null) {
return (
<ProductionProcessDetail
<ProductionProcessJobOrderDetail
jobOrderId={selectedProcessId}
onBack={() => setSelectedProcessId(null)}
/>
@@ -59,12 +44,11 @@ const ProductionProcessPage = () => {

return (
<ProductionProcessList
onSelectProcess={(jobOrderId, productProcessId) => {
printerCombo={printerCombo}
onSelectProcess={(jobOrderId) => {
const id = jobOrderId ?? null;
if (id !== null) {
setSelectedProcessId(id);
} else {
}
}}
/>


+ 359
- 228
src/components/ProductionProcess/ProductionProcessStepExecution.tsx View File

@@ -21,21 +21,24 @@ import StopIcon from "@mui/icons-material/Stop";
import PauseIcon from "@mui/icons-material/Pause";
import PlayArrowIcon from "@mui/icons-material/PlayArrow";
import { useTranslation } from "react-i18next";
import { ProductProcessLineDetailResponse, updateProductProcessLineQty,updateProductProcessLineQrscan,fetchProductProcessLineDetail ,UpdateProductProcessLineQtyRequest} from "@/app/api/jo/actions";
import { JobOrderProcessLineDetailResponse, updateProductProcessLineQty,updateProductProcessLineQrscan,fetchProductProcessLineDetail ,UpdateProductProcessLineQtyRequest} from "@/app/api/jo/actions";
import { Operator, Machine } from "@/app/api/jo";
import React, { useCallback, useEffect, useState } from "react";
import { useQrCodeScannerContext } from "../QrCodeScannerProvider/QrCodeScannerProvider";
import { fetchNameList, NameList } from "@/app/api/user/actions";
interface ProductionProcessStepExecutionProps {
lineId: number | null
onBack: () => void
//onClose: () => void
// onOutputSubmitted: () => Promise<void>
}
const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionProps> = ({
lineId,
onBack,
}) => {
const { t } = useTranslation();
const [lineDetail, setLineDetail] = useState<ProductProcessLineDetailResponse | null>(null);
const [lineDetail, setLineDetail] = useState<JobOrderProcessLineDetailResponse | null>(null);
const isCompleted = lineDetail?.status === "Completed";
const [outputData, setOutputData] = useState<UpdateProductProcessLineQtyRequest & {
byproductName: string;
byproductQty: number;
@@ -62,10 +65,39 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
const equipmentName = (lineDetail as any)?.equipment || lineDetail?.equipmentType || "-";
// 检查是否两个都已扫描
const bothScanned = lineDetail?.operatorId && lineDetail?.equipmentId;
//const bothScanned = lineDetail?.operatorId && lineDetail?.equipmentId;


useEffect(() => {
if (!lineId) {
setLineDetail(null);
return;
}
fetchProductProcessLineDetail(lineId)
.then((detail) => {
setLineDetail(detail as any);
// 初始化 outputData 从 lineDetail
setOutputData(prev => ({
...prev,
productProcessLineId: detail.id,
outputFromProcessQty: (detail as any).outputFromProcessQty || 0, // 取消注释,使用类型断言
outputFromProcessUom: (detail as any).outputFromProcessUom || "", // 取消注释,使用类型断言
defectQty: detail.defectQty || 0,
defectUom: detail.defectUom || "",
scrapQty: detail.scrapQty || 0,
scrapUom: detail.scrapUom || "",
byproductName: detail.byproductName || "",
byproductQty: detail.byproductQty || 0,
byproductUom: detail.byproductUom || ""
}));
})
.catch(err => {
console.error("Failed to load line detail", err);
setLineDetail(null);
});
}, [lineId]);
const handleSubmitOutput = async () => {
if (!lineDetail?.id) return;

@@ -88,27 +120,15 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro

console.log(" Output data submitted successfully");

} catch (error) {
console.error("Error submitting output:", error);
alert("Failed to submit output data. Please try again.");
}
};
useEffect(() => {
if (!lineId) {
setLineDetail(null);
return;
}
fetchProductProcessLineDetail(lineId)
fetchProductProcessLineDetail(lineDetail.id)
.then((detail) => {
setLineDetail(detail);
setLineDetail(detail as any);
// 初始化 outputData 从 lineDetail
setOutputData(prev => ({
...prev,
productProcessLineId: detail.id,
//outputFromProcessQty: detail.outputFromProcessQty || 0,
// outputFromProcessUom: detail.outputFromProcessUom || "",
outputFromProcessQty: (detail as any).outputFromProcessQty || 0, // 取消注释,使用类型断言
outputFromProcessUom: (detail as any).outputFromProcessUom || "", // 取消注释,使用类型断言
defectQty: detail.defectQty || 0,
defectUom: detail.defectUom || "",
scrapQty: detail.scrapQty || 0,
@@ -122,8 +142,12 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro
console.error("Failed to load line detail", err);
setLineDetail(null);
});
}, [lineId]);

} catch (error) {
console.error("Error submitting output:", error);
alert("Failed to submit output data. Please try again.");
}
};
// 处理 QR 码扫描效果
useEffect(() => {
if (isManualScanning && qrValues.length > 0 && lineDetail?.id) {
@@ -159,245 +183,352 @@ const ProductionProcessStepExecution: React.FC<ProductionProcessStepExecutionPro

return (
<Box>
{/* 当前步骤信息 */}
<Grid container spacing={2} sx={{ mb: 3 }}>
<Grid item xs={12} md={6}>
<Card sx={{ bgcolor: 'primary.50', border: '2px solid', borderColor: 'primary.main', height: '100%' }}>
<CardContent>
<Typography variant="h6" color="primary.main" gutterBottom>
{t("Executing")}: {lineDetail?.name} (Seq: {lineDetail?.seqNo})
</Typography>
<Typography variant="body2" color="text.secondary">
{lineDetail?.description}
</Typography>
<Typography variant="body2" color="text.secondary">
{t("Operator")}: {lineDetail?.operatorName || "-"}
</Typography>
<Typography variant="body2" color="text.secondary">
{t("Equipment")}: {equipmentName}
</Typography>
<Stack direction="row" spacing={2} justifyContent="center">
<Button
variant="contained"
color="error"
startIcon={<StopIcon />}
onClick={handleStop}
>
{t("Stop")}
</Button>
{!isPaused ? (
<Button
variant="contained"
color="warning"
startIcon={<PauseIcon />}
onClick={handlePause}
>
{t("Pause")}
</Button>
) : (
<Button
variant="contained"
color="success"
startIcon={<PlayArrowIcon />}
onClick={handleContinue}
>
{t("Continue")}
</Button>
)}
</Stack>
</CardContent>
</Card>
</Grid>
</Grid>
<Box sx={{ mb: 2 }}>
<Button variant="outlined" onClick={onBack}>
{t("Back to List")}
</Button>
</Box>

{/* 如果已完成,显示合并的视图 */}
{isCompleted ? (
<Card sx={{ bgcolor: 'success.50', border: '2px solid', borderColor: 'success.main', mb: 3 }}>
<CardContent>
<Typography variant="h5" color="success.main" gutterBottom fontWeight="bold">
{t("Completed Step")}: {lineDetail?.name} (Seq: {lineDetail?.seqNo})
</Typography>
{/*<Divider sx={{ my: 2 }} />*/}
{/* 步骤信息部分 */}
<Typography variant="h6" gutterBottom sx={{ mt: 2 }}>
{t("Step Information")}
</Typography>
<Grid container spacing={2} sx={{ mb: 3 }}>
<Grid item xs={12} md={6}>
<Typography variant="body2" color="text.secondary">
<strong>{t("Description")}:</strong> {lineDetail?.description || "-"}
</Typography>
</Grid>
<Grid item xs={12} md={6}>
<Typography variant="body2" color="text.secondary">
<strong>{t("Operator")}:</strong> {lineDetail?.operatorName || "-"}
</Typography>
</Grid>
<Grid item xs={12} md={6}>
<Typography variant="body2" color="text.secondary">
<strong>{t("Equipment")}:</strong> {equipmentName}
</Typography>
</Grid>
<Grid item xs={12} md={6}>
<Typography variant="body2" color="text.secondary">
<strong>{t("Status")}:</strong> {lineDetail?.status || "-"}
</Typography>
</Grid>
</Grid>

{/*<Divider sx={{ my: 2 }} />*/}

{/* ========== 产出输入表单 ========== */}
{bothScanned && (
<Box>
<Box sx={{ mb: 2, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="h6" fontWeight={600}>
{t("Production Output Data Entry")}
{/* 产出数据部分 */}
<Typography variant="h6" gutterBottom sx={{ mt: 2 }}>
{t("Production Output Data")}
</Typography>
<Button
variant="outlined"
onClick={() => setShowOutputTable(!showOutputTable)}
>
{showOutputTable ? t("Hide Table") : t("Show Table")}
</Button>
</Box>
{showOutputTable && (
<Paper sx={{ p: 3, bgcolor: 'grey.50' }}>
<Table size="small">
<TableHead>
<TableRow>
<TableCell width="30%">{t("Type")}</TableCell>
<TableCell width="35%">{t("Quantity")}</TableCell>
<TableCell width="35%">{t("Unit")}</TableCell>
</TableRow>
</TableHead>
<TableBody>
{/* start line output */}
<Table size="small" sx={{ mt: 2 }}>
<TableHead>
<TableRow>
<TableCell width="30%"><strong>{t("Type")}</strong></TableCell>
<TableCell width="35%"><strong>{t("Quantity")}</strong></TableCell>
<TableCell width="35%"><strong>{t("Unit")}</strong></TableCell>
</TableRow>
</TableHead>
<TableBody>
{/* Output from Process */}
<TableRow>
<TableCell>
<Typography fontWeight={500}>{t("Output from Process")}</Typography>
</TableCell>
<TableCell>
<Typography>{lineDetail?.outputFromProcessQty || 0}</Typography>
</TableCell>
<TableCell>
<Typography>{lineDetail?.outputFromProcessUom || "-"}</Typography>
</TableCell>
</TableRow>
{/* By-product */}
{lineDetail?.byproductQty && lineDetail.byproductQty > 0 && (
<TableRow>
<TableCell>
<Typography fontWeight={500}>{t("Output from Process")}</Typography>
<Typography fontWeight={500}>{t("By-product")}</Typography>
{lineDetail.byproductName && (
<Typography variant="caption" color="text.secondary">
({lineDetail.byproductName})
</Typography>
)}
</TableCell>
<TableCell>
<TextField
type="number"
fullWidth
size="small"
value={outputData.outputFromProcessQty}
onChange={(e) => setOutputData({
...outputData,
outputFromProcessQty: parseInt(e.target.value) || 0
})}
/>
<Typography>{lineDetail.byproductQty}</Typography>
</TableCell>
<TableCell>
<TextField
fullWidth
size="small"
value={outputData.outputFromProcessUom}
onChange={(e) => setOutputData({
...outputData,
outputFromProcessUom: e.target.value
})}
//placeholder="KG, L, PCS..."
/>
</TableCell>
</TableRow>
{/* byproduct */}
<TableRow>
<TableCell>
<Stack>
<Typography fontWeight={500}>{t("By-product")}</Typography>
<TextField
fullWidth
size="small"
value={outputData.byproductName}
onChange={(e) => setOutputData({
...outputData,
byproductName: e.target.value
})}
placeholder={t("By-product name")}
sx={{ mt: 1 }}
/>
</Stack>
</TableCell>
<TableCell>
<TextField
type="number"
fullWidth
size="small"
value={outputData.byproductQty}
onChange={(e) => setOutputData({
...outputData,
byproductQty: parseInt(e.target.value) || 0
})}
/>
</TableCell>
<TableCell>
<TextField
fullWidth
size="small"
value={outputData.byproductUom}
onChange={(e) => setOutputData({
...outputData,
byproductUom: e.target.value
})}
//placeholder="KG, L, PCS..."
/>
<Typography>{lineDetail.byproductUom || "-"}</Typography>
</TableCell>
</TableRow>
)}

{/* defect */}
{/* Defect */}
{lineDetail?.defectQty && lineDetail.defectQty > 0 && (
<TableRow sx={{ bgcolor: 'warning.50' }}>
<TableCell>
<Typography fontWeight={500} color="warning.dark">{t("Defect")}</Typography>
</TableCell>
<TableCell>
<TextField
type="number"
fullWidth
size="small"
value={outputData.defectQty}
onChange={(e) => setOutputData({
...outputData,
defectQty: parseInt(e.target.value) || 0
})}
/>
<Typography>{lineDetail.defectQty}</Typography>
</TableCell>
<TableCell>
<TextField
fullWidth
size="small"
value={outputData.defectUom}
onChange={(e) => setOutputData({
...outputData,
defectUom: e.target.value
})}
//placeholder="KG, L, PCS..."
/>
<Typography>{lineDetail.defectUom || "-"}</Typography>
</TableCell>
</TableRow>
)}

{/* scrap */}
{/* Scrap */}
{lineDetail?.scrapQty && lineDetail.scrapQty > 0 && (
<TableRow sx={{ bgcolor: 'error.50' }}>
<TableCell>
<Typography fontWeight={500} color="error.dark">{t("Scrap")}</Typography>
</TableCell>
<TableCell>
<TextField
type="number"
fullWidth
size="small"
value={outputData.scrapQty}
onChange={(e) => setOutputData({
...outputData,
scrapQty: parseInt(e.target.value) || 0
})}
/>
<Typography>{lineDetail.scrapQty}</Typography>
</TableCell>
<TableCell>
<TextField
fullWidth
size="small"
value={outputData.scrapUom}
onChange={(e) => setOutputData({
...outputData,
scrapUom: e.target.value
})}
//placeholder="KG, L, PCS..."
/>
<Typography>{lineDetail.scrapUom || "-"}</Typography>
</TableCell>
</TableRow>
</TableBody>
</Table>
)}
</TableBody>
</Table>
</CardContent>
</Card>
) : (
<>
{/* 如果未完成,显示原来的两个部分 */}
{/* 当前步骤信息 */}
<Grid container spacing={2} sx={{ mb: 3 }}>
<Grid item xs={12} md={6}>
<Card sx={{ bgcolor: 'primary.50', border: '2px solid', borderColor: 'primary.main', height: '100%' }}>
<CardContent>
<Typography variant="h6" color="primary.main" gutterBottom>
{t("Executing")}: {lineDetail?.name} (Seq: {lineDetail?.seqNo})
</Typography>
<Typography variant="body2" color="text.secondary">
{lineDetail?.description}
</Typography>
<Typography variant="body2" color="text.secondary">
{t("Operator")}: {lineDetail?.operatorName || "-"}
</Typography>
<Typography variant="body2" color="text.secondary">
{t("Equipment")}: {equipmentName}
</Typography>
<Stack direction="row" spacing={2} justifyContent="center" sx={{ mt: 2 }}>
<Button
variant="contained"
color="error"
startIcon={<StopIcon />}
onClick={handleStop}
>
{t("Stop")}
</Button>
{!isPaused ? (
<Button
variant="contained"
color="warning"
startIcon={<PauseIcon />}
onClick={handlePause}
>
{t("Pause")}
</Button>
) : (
<Button
variant="contained"
color="success"
startIcon={<PlayArrowIcon />}
onClick={handleContinue}
>
{t("Continue")}
</Button>
)}
</Stack>
</CardContent>
</Card>
</Grid>
</Grid>

{/* ========== 产出输入表单 ========== */}
<Box>
<Box sx={{ mb: 2, display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<Typography variant="h6" fontWeight={600}>
{t("Production Output Data Entry")}
</Typography>
<Button
variant="outlined"
onClick={() => setShowOutputTable(!showOutputTable)}
>
{showOutputTable ? t("Hide Table") : t("Show Table")}
</Button>
</Box>
{showOutputTable && (
<Paper sx={{ p: 3, bgcolor: 'grey.50' }}>
<Table size="small">
<TableHead>
<TableRow>
<TableCell width="30%">{t("Type")}</TableCell>
<TableCell width="35%">{t("Quantity")}</TableCell>
<TableCell width="35%">{t("Unit")}</TableCell>
</TableRow>
</TableHead>
<TableBody>
{/* start line output */}
<TableRow>
<TableCell>
<Typography fontWeight={500}>{t("Output from Process")}</Typography>
</TableCell>
<TableCell>
<TextField
type="number"
fullWidth
size="small"
value={outputData.outputFromProcessQty}
onChange={(e) => setOutputData({
...outputData,
outputFromProcessQty: parseInt(e.target.value) || 0
})}
/>
</TableCell>
<TableCell>
<TextField
fullWidth
size="small"
value={outputData.outputFromProcessUom}
onChange={(e) => setOutputData({
...outputData,
outputFromProcessUom: e.target.value
})}
/>
</TableCell>
</TableRow>
{/* byproduct */}
<TableRow>
<TableCell>
<Stack>
<Typography fontWeight={500}>{t("By-product")}</Typography>
</Stack>
</TableCell>
<TableCell>
<TextField
type="number"
fullWidth
size="small"
value={outputData.byproductQty}
onChange={(e) => setOutputData({
...outputData,
byproductQty: parseInt(e.target.value) || 0
})}
/>
</TableCell>
<TableCell>
<TextField
fullWidth
size="small"
value={outputData.byproductUom}
onChange={(e) => setOutputData({
...outputData,
byproductUom: e.target.value
})}
/>
</TableCell>
</TableRow>

{/* submit button */}
<Box sx={{ mt: 3, display: 'flex', gap: 2 }}>
<Button
variant="outlined"
onClick={() => setShowOutputTable(false)}
>
{t("Cancel")}
</Button>
<Button
variant="contained"
startIcon={<CheckCircleIcon />}
onClick={handleSubmitOutput}
>
{t("Complete Step")}
</Button>
</Box>
</Paper>
)}
</Box>
{/* defect */}
<TableRow sx={{ bgcolor: 'warning.50' }}>
<TableCell>
<Typography fontWeight={500} color="warning.dark">{t("Defect")}</Typography>
</TableCell>
<TableCell>
<TextField
type="number"
fullWidth
size="small"
value={outputData.defectQty}
onChange={(e) => setOutputData({
...outputData,
defectQty: parseInt(e.target.value) || 0
})}
/>
</TableCell>
<TableCell>
<TextField
fullWidth
size="small"
value={outputData.defectUom}
onChange={(e) => setOutputData({
...outputData,
defectUom: e.target.value
})}
/>
</TableCell>
</TableRow>

{/* scrap */}
<TableRow sx={{ bgcolor: 'error.50' }}>
<TableCell>
<Typography fontWeight={500} color="error.dark">{t("Scrap")}</Typography>
</TableCell>
<TableCell>
<TextField
type="number"
fullWidth
size="small"
value={outputData.scrapQty}
onChange={(e) => setOutputData({
...outputData,
scrapQty: parseInt(e.target.value) || 0
})}
/>
</TableCell>
<TableCell>
<TextField
fullWidth
size="small"
value={outputData.scrapUom}
onChange={(e) => setOutputData({
...outputData,
scrapUom: e.target.value
})}
/>
</TableCell>
</TableRow>
</TableBody>
</Table>

{/* submit button */}
<Box sx={{ mt: 3, display: 'flex', gap: 2 }}>
<Button
variant="outlined"
onClick={() => setShowOutputTable(false)}
>
{t("Cancel")}
</Button>
<Button
variant="contained"
startIcon={<CheckCircleIcon />}
onClick={handleSubmitOutput}
>
{t("Complete Step")}
</Button>
</Box>
</Paper>
)}
</Box>
</>
)}
</Box>
);


Loading…
Cancel
Save