|
|
@@ -16,13 +16,27 @@ import { |
|
|
TableRow, |
|
|
TableRow, |
|
|
TableCell, |
|
|
TableCell, |
|
|
TableBody, |
|
|
TableBody, |
|
|
|
|
|
Button, |
|
|
|
|
|
TextField, |
|
|
|
|
|
Checkbox, |
|
|
|
|
|
FormControlLabel, |
|
|
|
|
|
IconButton, |
|
|
} from "@mui/material"; |
|
|
} from "@mui/material"; |
|
|
import type { BomCombo, BomDetailResponse } from "@/app/api/bom"; |
|
|
import type { BomCombo, BomDetailResponse } from "@/app/api/bom"; |
|
|
import { fetchBomComboClient, fetchBomDetailClient } from "@/app/api/bom/client"; |
|
|
|
|
|
|
|
|
import { |
|
|
|
|
|
editBomClient, |
|
|
|
|
|
fetchBomComboClient, |
|
|
|
|
|
fetchBomDetailClient, |
|
|
|
|
|
} from "@/app/api/bom/client"; |
|
|
import type { SelectChangeEvent } from "@mui/material/Select"; |
|
|
import type { SelectChangeEvent } from "@mui/material/Select"; |
|
|
import { useTranslation } from "react-i18next"; |
|
|
import { useTranslation } from "react-i18next"; |
|
|
import SearchBox, { Criterion } from "../SearchBox"; |
|
|
import SearchBox, { Criterion } from "../SearchBox"; |
|
|
import { useMemo, useCallback } from "react"; |
|
|
import { useMemo, useCallback } from "react"; |
|
|
|
|
|
import AddIcon from "@mui/icons-material/Add"; |
|
|
|
|
|
import SaveIcon from "@mui/icons-material/Save"; |
|
|
|
|
|
import CancelIcon from "@mui/icons-material/Cancel"; |
|
|
|
|
|
import DeleteIcon from "@mui/icons-material/Delete"; |
|
|
|
|
|
import EditIcon from "@mui/icons-material/Edit"; |
|
|
const ImportBomDetailTab: React.FC = () => { |
|
|
const ImportBomDetailTab: React.FC = () => { |
|
|
const { t } = useTranslation( "common" ); |
|
|
const { t } = useTranslation( "common" ); |
|
|
const [bomList, setBomList] = useState<BomCombo[]>([]); |
|
|
const [bomList, setBomList] = useState<BomCombo[]>([]); |
|
|
@@ -32,6 +46,89 @@ const ImportBomDetailTab: React.FC = () => { |
|
|
const [loadingDetail, setLoadingDetail] = useState(false); |
|
|
const [loadingDetail, setLoadingDetail] = useState(false); |
|
|
const [filteredBoms, setFilteredBoms] = useState<BomCombo[]>([]) |
|
|
const [filteredBoms, setFilteredBoms] = useState<BomCombo[]>([]) |
|
|
const [currentBom, setCurrentBom] = useState<BomCombo | null>(null); |
|
|
const [currentBom, setCurrentBom] = useState<BomCombo | null>(null); |
|
|
|
|
|
|
|
|
|
|
|
type EditMaterialRow = { |
|
|
|
|
|
key: string; |
|
|
|
|
|
id?: number; |
|
|
|
|
|
itemCode?: string; |
|
|
|
|
|
itemName?: string; |
|
|
|
|
|
qty: number; |
|
|
|
|
|
isConsumable: boolean; |
|
|
|
|
|
|
|
|
|
|
|
baseUom?: string; |
|
|
|
|
|
stockQty?: number; |
|
|
|
|
|
stockUom?: string; |
|
|
|
|
|
salesQty?: number; |
|
|
|
|
|
salesUom?: string; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
type EditProcessRow = { |
|
|
|
|
|
key: string; |
|
|
|
|
|
id?: number; |
|
|
|
|
|
seqNo?: number; |
|
|
|
|
|
processCode?: string; |
|
|
|
|
|
processName?: string; |
|
|
|
|
|
description: string; |
|
|
|
|
|
equipmentCode?: string; |
|
|
|
|
|
durationInMinute: number; |
|
|
|
|
|
prepTimeInMinute: number; |
|
|
|
|
|
postProdTimeInMinute: number; |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const [isEditing, setIsEditing] = useState(false); |
|
|
|
|
|
const [editLoading, setEditLoading] = useState(false); |
|
|
|
|
|
const [editError, setEditError] = useState<string | null>(null); |
|
|
|
|
|
const [editBasic, setEditBasic] = useState<{ |
|
|
|
|
|
description: string; |
|
|
|
|
|
outputQty: number; |
|
|
|
|
|
outputQtyUom: string; |
|
|
|
|
|
|
|
|
|
|
|
isDark: number; |
|
|
|
|
|
isFloat: number; |
|
|
|
|
|
isDense: number; |
|
|
|
|
|
scrapRate: number; |
|
|
|
|
|
allergicSubstances: number; |
|
|
|
|
|
timeSequence: number; |
|
|
|
|
|
complexity: number; |
|
|
|
|
|
isDrink: boolean; |
|
|
|
|
|
} | null>(null); |
|
|
|
|
|
|
|
|
|
|
|
const [editMaterials, setEditMaterials] = useState<EditMaterialRow[]>([]); |
|
|
|
|
|
const [editProcesses, setEditProcesses] = useState<EditProcessRow[]>([]); |
|
|
|
|
|
|
|
|
|
|
|
// Process add form (uses dropdown selections). |
|
|
|
|
|
const [processAddForm, setProcessAddForm] = useState<{ |
|
|
|
|
|
processCode: string; |
|
|
|
|
|
equipmentCode: string; |
|
|
|
|
|
description: string; |
|
|
|
|
|
durationInMinute: number; |
|
|
|
|
|
prepTimeInMinute: number; |
|
|
|
|
|
postProdTimeInMinute: number; |
|
|
|
|
|
}>({ |
|
|
|
|
|
processCode: "", |
|
|
|
|
|
equipmentCode: "", |
|
|
|
|
|
description: "", |
|
|
|
|
|
durationInMinute: 0, |
|
|
|
|
|
prepTimeInMinute: 0, |
|
|
|
|
|
postProdTimeInMinute: 0, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
const processCodeOptions = useMemo(() => { |
|
|
|
|
|
const codes = new Set<string>(); |
|
|
|
|
|
(detail?.processes ?? []).forEach((p) => { |
|
|
|
|
|
if (p.processCode) codes.add(p.processCode); |
|
|
|
|
|
}); |
|
|
|
|
|
return Array.from(codes); |
|
|
|
|
|
}, [detail]); |
|
|
|
|
|
|
|
|
|
|
|
const equipmentCodeOptions = useMemo(() => { |
|
|
|
|
|
const codes = new Set<string>(); |
|
|
|
|
|
(detail?.processes ?? []).forEach((p) => { |
|
|
|
|
|
if (p.equipmentCode) codes.add(p.equipmentCode); |
|
|
|
|
|
}); |
|
|
|
|
|
return Array.from(codes); |
|
|
|
|
|
}, [detail]); |
|
|
|
|
|
|
|
|
useEffect(() => { |
|
|
useEffect(() => { |
|
|
const loadList = async () => { |
|
|
const loadList = async () => { |
|
|
setLoadingList(true); |
|
|
setLoadingList(true); |
|
|
@@ -143,6 +240,201 @@ const ImportBomDetailTab: React.FC = () => { |
|
|
} |
|
|
} |
|
|
}; |
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const genKey = () => Math.random().toString(36).slice(2); |
|
|
|
|
|
|
|
|
|
|
|
const startEdit = useCallback(() => { |
|
|
|
|
|
if (!detail) return; |
|
|
|
|
|
|
|
|
|
|
|
setEditError(null); |
|
|
|
|
|
setEditBasic({ |
|
|
|
|
|
description: detail.description ?? "", |
|
|
|
|
|
outputQty: detail.outputQty ?? 0, |
|
|
|
|
|
outputQtyUom: detail.outputQtyUom ?? "", |
|
|
|
|
|
|
|
|
|
|
|
isDark: detail.isDark ?? 0, |
|
|
|
|
|
isFloat: detail.isFloat ?? 0, |
|
|
|
|
|
isDense: detail.isDense ?? 0, |
|
|
|
|
|
scrapRate: detail.scrapRate ?? 0, |
|
|
|
|
|
allergicSubstances: detail.allergicSubstances ?? 0, |
|
|
|
|
|
timeSequence: detail.timeSequence ?? 0, |
|
|
|
|
|
complexity: detail.complexity ?? 0, |
|
|
|
|
|
isDrink: detail.isDrink ?? false, |
|
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
|
|
setEditMaterials( |
|
|
|
|
|
(detail.materials ?? []).map((m) => ({ |
|
|
|
|
|
key: genKey(), |
|
|
|
|
|
id: undefined, |
|
|
|
|
|
itemCode: m.itemCode ?? "", |
|
|
|
|
|
itemName: m.itemName ?? "", |
|
|
|
|
|
qty: m.baseQty ?? 0, |
|
|
|
|
|
isConsumable: m.isConsumable ?? false, |
|
|
|
|
|
baseUom: m.baseUom, |
|
|
|
|
|
stockQty: m.stockQty, |
|
|
|
|
|
stockUom: m.stockUom, |
|
|
|
|
|
salesQty: m.salesQty, |
|
|
|
|
|
salesUom: m.salesUom, |
|
|
|
|
|
})), |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
setEditProcesses( |
|
|
|
|
|
(detail.processes ?? []).map((p) => ({ |
|
|
|
|
|
key: genKey(), |
|
|
|
|
|
id: undefined, |
|
|
|
|
|
seqNo: p.seqNo, |
|
|
|
|
|
processCode: p.processCode ?? "", |
|
|
|
|
|
processName: p.processName, |
|
|
|
|
|
description: p.processDescription ?? "", |
|
|
|
|
|
equipmentCode: p.equipmentCode ?? p.equipmentName ?? "", |
|
|
|
|
|
durationInMinute: p.durationInMinute ?? 0, |
|
|
|
|
|
prepTimeInMinute: p.prepTimeInMinute ?? 0, |
|
|
|
|
|
postProdTimeInMinute: p.postProdTimeInMinute ?? 0, |
|
|
|
|
|
})), |
|
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
|
|
setIsEditing(true); |
|
|
|
|
|
}, [detail]); |
|
|
|
|
|
|
|
|
|
|
|
const cancelEdit = useCallback(() => { |
|
|
|
|
|
setIsEditing(false); |
|
|
|
|
|
setEditLoading(false); |
|
|
|
|
|
setEditError(null); |
|
|
|
|
|
setEditBasic(null); |
|
|
|
|
|
setEditMaterials([]); |
|
|
|
|
|
setEditProcesses([]); |
|
|
|
|
|
setProcessAddForm({ |
|
|
|
|
|
processCode: "", |
|
|
|
|
|
equipmentCode: "", |
|
|
|
|
|
description: "", |
|
|
|
|
|
durationInMinute: 0, |
|
|
|
|
|
prepTimeInMinute: 0, |
|
|
|
|
|
postProdTimeInMinute: 0, |
|
|
|
|
|
}); |
|
|
|
|
|
}, []); |
|
|
|
|
|
|
|
|
|
|
|
const addMaterialRow = useCallback(() => { |
|
|
|
|
|
setEditMaterials((prev) => [ |
|
|
|
|
|
...prev, |
|
|
|
|
|
{ |
|
|
|
|
|
key: genKey(), |
|
|
|
|
|
itemCode: "", |
|
|
|
|
|
itemName: "", |
|
|
|
|
|
qty: 0, |
|
|
|
|
|
isConsumable: false, |
|
|
|
|
|
baseUom: "", |
|
|
|
|
|
stockQty: undefined, |
|
|
|
|
|
stockUom: "", |
|
|
|
|
|
salesQty: undefined, |
|
|
|
|
|
salesUom: "", |
|
|
|
|
|
}, |
|
|
|
|
|
]); |
|
|
|
|
|
}, []); |
|
|
|
|
|
|
|
|
|
|
|
const addProcessRow = useCallback(() => { |
|
|
|
|
|
setEditProcesses((prev) => [ |
|
|
|
|
|
...prev, |
|
|
|
|
|
{ |
|
|
|
|
|
key: genKey(), |
|
|
|
|
|
seqNo: undefined, |
|
|
|
|
|
processCode: "", |
|
|
|
|
|
processName: "", |
|
|
|
|
|
description: "", |
|
|
|
|
|
equipmentCode: "", |
|
|
|
|
|
durationInMinute: 0, |
|
|
|
|
|
prepTimeInMinute: 0, |
|
|
|
|
|
postProdTimeInMinute: 0, |
|
|
|
|
|
}, |
|
|
|
|
|
]); |
|
|
|
|
|
}, []); |
|
|
|
|
|
|
|
|
|
|
|
const addProcessFromForm = useCallback(() => { |
|
|
|
|
|
const pCode = processAddForm.processCode.trim(); |
|
|
|
|
|
if (!pCode) { |
|
|
|
|
|
setEditError("請先選擇工序 Process Code"); |
|
|
|
|
|
return; |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
setEditProcesses((prev) => [ |
|
|
|
|
|
...prev, |
|
|
|
|
|
{ |
|
|
|
|
|
key: genKey(), |
|
|
|
|
|
seqNo: undefined, |
|
|
|
|
|
processCode: pCode, |
|
|
|
|
|
processName: "", |
|
|
|
|
|
description: processAddForm.description ?? "", |
|
|
|
|
|
equipmentCode: processAddForm.equipmentCode.trim(), |
|
|
|
|
|
durationInMinute: processAddForm.durationInMinute ?? 0, |
|
|
|
|
|
prepTimeInMinute: processAddForm.prepTimeInMinute ?? 0, |
|
|
|
|
|
postProdTimeInMinute: processAddForm.postProdTimeInMinute ?? 0, |
|
|
|
|
|
}, |
|
|
|
|
|
]); |
|
|
|
|
|
|
|
|
|
|
|
setProcessAddForm({ |
|
|
|
|
|
processCode: "", |
|
|
|
|
|
equipmentCode: "", |
|
|
|
|
|
description: "", |
|
|
|
|
|
durationInMinute: 0, |
|
|
|
|
|
prepTimeInMinute: 0, |
|
|
|
|
|
postProdTimeInMinute: 0, |
|
|
|
|
|
}); |
|
|
|
|
|
setEditError(null); |
|
|
|
|
|
}, [processAddForm]); |
|
|
|
|
|
|
|
|
|
|
|
const deleteMaterialRow = useCallback((key: string) => { |
|
|
|
|
|
setEditMaterials((prev) => prev.filter((r) => r.key !== key)); |
|
|
|
|
|
}, []); |
|
|
|
|
|
|
|
|
|
|
|
const deleteProcessRow = useCallback((key: string) => { |
|
|
|
|
|
setEditProcesses((prev) => prev.filter((r) => r.key !== key)); |
|
|
|
|
|
}, []); |
|
|
|
|
|
|
|
|
|
|
|
const handleSaveEdit = useCallback(async () => { |
|
|
|
|
|
if (!detail || !editBasic) return; |
|
|
|
|
|
setEditLoading(true); |
|
|
|
|
|
setEditError(null); |
|
|
|
|
|
|
|
|
|
|
|
try { |
|
|
|
|
|
for (const p of editProcesses) { |
|
|
|
|
|
if (!p.processCode?.trim()) { |
|
|
|
|
|
throw new Error("工序行 Process Code 不能为空"); |
|
|
|
|
|
} |
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
const payload: any = { |
|
|
|
|
|
description: editBasic.description || undefined, |
|
|
|
|
|
outputQty: editBasic.outputQty, |
|
|
|
|
|
outputQtyUom: editBasic.outputQtyUom || undefined, |
|
|
|
|
|
|
|
|
|
|
|
isDark: editBasic.isDark, |
|
|
|
|
|
isFloat: editBasic.isFloat, |
|
|
|
|
|
isDense: editBasic.isDense, |
|
|
|
|
|
scrapRate: editBasic.scrapRate, |
|
|
|
|
|
allergicSubstances: editBasic.allergicSubstances, |
|
|
|
|
|
timeSequence: editBasic.timeSequence, |
|
|
|
|
|
complexity: editBasic.complexity, |
|
|
|
|
|
isDrink: editBasic.isDrink, |
|
|
|
|
|
processes: editProcesses.map((p) => ({ |
|
|
|
|
|
id: p.id, |
|
|
|
|
|
seqNo: p.seqNo, |
|
|
|
|
|
processCode: p.processCode?.trim() || undefined, |
|
|
|
|
|
equipmentCode: p.equipmentCode?.trim() || undefined, |
|
|
|
|
|
description: p.description || undefined, |
|
|
|
|
|
durationInMinute: p.durationInMinute, |
|
|
|
|
|
prepTimeInMinute: p.prepTimeInMinute, |
|
|
|
|
|
postProdTimeInMinute: p.postProdTimeInMinute, |
|
|
|
|
|
})), |
|
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
const updated = await editBomClient(detail.id, payload); |
|
|
|
|
|
setDetail(updated); |
|
|
|
|
|
setIsEditing(false); |
|
|
|
|
|
} catch (e: any) { |
|
|
|
|
|
setEditError(e?.message || "保存失败"); |
|
|
|
|
|
} finally { |
|
|
|
|
|
setEditLoading(false); |
|
|
|
|
|
} |
|
|
|
|
|
}, [detail, editBasic, editProcesses]); |
|
|
|
|
|
|
|
|
return ( |
|
|
return ( |
|
|
<Stack spacing={2}> |
|
|
<Stack spacing={2}> |
|
|
<SearchBox<BomSearchKey> |
|
|
<SearchBox<BomSearchKey> |
|
|
@@ -175,33 +467,253 @@ const ImportBomDetailTab: React.FC = () => { |
|
|
|
|
|
|
|
|
{/* Basic Info 列表 */} |
|
|
{/* Basic Info 列表 */} |
|
|
<Paper variant="outlined" sx={{ p: 2 }}> |
|
|
<Paper variant="outlined" sx={{ p: 2 }}> |
|
|
<Typography variant="subtitle1" gutterBottom> |
|
|
|
|
|
{t("Basic Info")} |
|
|
|
|
|
</Typography> |
|
|
|
|
|
|
|
|
|
|
|
<Stack spacing={0.5}> |
|
|
|
|
|
{/* 第一行:輸出數量 + 類型 */} |
|
|
|
|
|
<Typography variant="body2"> |
|
|
|
|
|
{t("Output Quantity")}:{" "} |
|
|
|
|
|
{detail.outputQty} {detail.outputQtyUom} |
|
|
|
|
|
{" "} |
|
|
|
|
|
{t("Type")}: {detail.description ?? "-"} |
|
|
|
|
|
|
|
|
<Stack |
|
|
|
|
|
direction="row" |
|
|
|
|
|
alignItems="center" |
|
|
|
|
|
justifyContent="space-between" |
|
|
|
|
|
sx={{ mb: 1 }} |
|
|
|
|
|
> |
|
|
|
|
|
<Typography variant="subtitle1" gutterBottom> |
|
|
|
|
|
{t("Basic Info")} |
|
|
</Typography> |
|
|
</Typography> |
|
|
|
|
|
|
|
|
{/* 第二行:各種指標,排成一行 key:value, key:value */} |
|
|
|
|
|
<Typography variant="body2"> |
|
|
|
|
|
{t("Allergic Substances")}: {renderAllergic(detail.allergicSubstances)} |
|
|
|
|
|
{" "}{t("Depth")}: {detail.isDark ?? "-"} |
|
|
|
|
|
{" "}{t("Float")}: {renderIsFloat(detail.isFloat)} |
|
|
|
|
|
{" "}{t("Density")}: {renderIsDense(detail.isDense)} |
|
|
|
|
|
</Typography> |
|
|
|
|
|
|
|
|
{!isEditing ? ( |
|
|
|
|
|
<Button |
|
|
|
|
|
size="small" |
|
|
|
|
|
startIcon={<EditIcon />} |
|
|
|
|
|
variant="outlined" |
|
|
|
|
|
onClick={startEdit} |
|
|
|
|
|
> |
|
|
|
|
|
{t("Edit")} |
|
|
|
|
|
</Button> |
|
|
|
|
|
) : ( |
|
|
|
|
|
<Stack direction="row" spacing={1}> |
|
|
|
|
|
<Button |
|
|
|
|
|
size="small" |
|
|
|
|
|
startIcon={<SaveIcon />} |
|
|
|
|
|
variant="contained" |
|
|
|
|
|
disabled={editLoading} |
|
|
|
|
|
onClick={handleSaveEdit} |
|
|
|
|
|
> |
|
|
|
|
|
{editLoading ? t("Saving...") : t("Save")} |
|
|
|
|
|
</Button> |
|
|
|
|
|
<Button |
|
|
|
|
|
size="small" |
|
|
|
|
|
startIcon={<CancelIcon />} |
|
|
|
|
|
variant="outlined" |
|
|
|
|
|
disabled={editLoading} |
|
|
|
|
|
onClick={cancelEdit} |
|
|
|
|
|
> |
|
|
|
|
|
{t("Cancel")} |
|
|
|
|
|
</Button> |
|
|
|
|
|
</Stack> |
|
|
|
|
|
)} |
|
|
|
|
|
</Stack> |
|
|
|
|
|
|
|
|
<Typography variant="body2"> |
|
|
|
|
|
{t("Time Sequence")}: {renderTimeSequence(detail.timeSequence)} |
|
|
|
|
|
{" "}{t("Complexity")}: {renderComplexity(detail.complexity)} |
|
|
|
|
|
{" "}{t("Base Score")}: {detail.baseScore ?? "-"} |
|
|
|
|
|
|
|
|
{editError && ( |
|
|
|
|
|
<Typography variant="body2" color="error"> |
|
|
|
|
|
{editError} |
|
|
</Typography> |
|
|
</Typography> |
|
|
</Stack> |
|
|
|
|
|
|
|
|
)} |
|
|
|
|
|
|
|
|
|
|
|
{!isEditing && ( |
|
|
|
|
|
<Stack spacing={0.5}> |
|
|
|
|
|
{/* 第一行:輸出數量 + 類型 */} |
|
|
|
|
|
<Typography variant="body2"> |
|
|
|
|
|
{t("Output Quantity")}: {detail.outputQty} {detail.outputQtyUom} |
|
|
|
|
|
{" "} |
|
|
|
|
|
{t("Type")}: {detail.description ?? "-"} |
|
|
|
|
|
</Typography> |
|
|
|
|
|
|
|
|
|
|
|
{/* 第二行:各種指標,排成一行 key:value, key:value */} |
|
|
|
|
|
<Typography variant="body2"> |
|
|
|
|
|
{t("Allergic Substances")}:{" "} |
|
|
|
|
|
{renderAllergic(detail.allergicSubstances)} |
|
|
|
|
|
{" "}{t("Depth")}: {detail.isDark ?? "-"} |
|
|
|
|
|
{" "}{t("Float")}: {renderIsFloat(detail.isFloat)} |
|
|
|
|
|
{" "}{t("Density")}: {renderIsDense(detail.isDense)} |
|
|
|
|
|
</Typography> |
|
|
|
|
|
|
|
|
|
|
|
<Typography variant="body2"> |
|
|
|
|
|
{t("Time Sequence")}: {renderTimeSequence(detail.timeSequence)} |
|
|
|
|
|
{" "}{t("Complexity")}: {renderComplexity(detail.complexity)} |
|
|
|
|
|
{" "}{t("Base Score")}: {detail.baseScore ?? "-"} |
|
|
|
|
|
</Typography> |
|
|
|
|
|
</Stack> |
|
|
|
|
|
)} |
|
|
|
|
|
|
|
|
|
|
|
{isEditing && editBasic && ( |
|
|
|
|
|
<Stack spacing={1}> |
|
|
|
|
|
<TextField |
|
|
|
|
|
size="small" |
|
|
|
|
|
label={t("Type")} |
|
|
|
|
|
value={editBasic.description} |
|
|
|
|
|
onChange={(e) => |
|
|
|
|
|
setEditBasic((p) => (p ? { ...p, description: e.target.value } : p)) |
|
|
|
|
|
} |
|
|
|
|
|
fullWidth |
|
|
|
|
|
/> |
|
|
|
|
|
|
|
|
|
|
|
<Stack direction="row" spacing={1}> |
|
|
|
|
|
<TextField |
|
|
|
|
|
size="small" |
|
|
|
|
|
label={t("Output Quantity")} |
|
|
|
|
|
type="number" |
|
|
|
|
|
value={editBasic.outputQty} |
|
|
|
|
|
onChange={(e) => |
|
|
|
|
|
setEditBasic((p) => |
|
|
|
|
|
p ? { ...p, outputQty: Number(e.target.value) } : p |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
/> |
|
|
|
|
|
<TextField |
|
|
|
|
|
size="small" |
|
|
|
|
|
label={t("Output Quantity UOM")} |
|
|
|
|
|
value={editBasic.outputQtyUom} |
|
|
|
|
|
onChange={(e) => |
|
|
|
|
|
setEditBasic((p) => |
|
|
|
|
|
p ? { ...p, outputQtyUom: e.target.value } : p |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
/> |
|
|
|
|
|
</Stack> |
|
|
|
|
|
|
|
|
|
|
|
<Stack direction="row" spacing={1}> |
|
|
|
|
|
<TextField |
|
|
|
|
|
size="small" |
|
|
|
|
|
label={t("Scrap Rate")} |
|
|
|
|
|
type="number" |
|
|
|
|
|
value={editBasic.scrapRate} |
|
|
|
|
|
onChange={(e) => |
|
|
|
|
|
setEditBasic((p) => |
|
|
|
|
|
p ? { ...p, scrapRate: Number(e.target.value) } : p |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
/> |
|
|
|
|
|
<FormControl size="small" sx={{ minWidth: 160 }}> |
|
|
|
|
|
<InputLabel>{t("Allergic Substances")}</InputLabel> |
|
|
|
|
|
<Select |
|
|
|
|
|
label={t("Allergic Substances")} |
|
|
|
|
|
value={editBasic.allergicSubstances} |
|
|
|
|
|
onChange={(e) => |
|
|
|
|
|
setEditBasic((p) => |
|
|
|
|
|
p |
|
|
|
|
|
? { |
|
|
|
|
|
...p, |
|
|
|
|
|
allergicSubstances: Number(e.target.value), |
|
|
|
|
|
} |
|
|
|
|
|
: p, |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
> |
|
|
|
|
|
<MenuItem value={0}>有</MenuItem> |
|
|
|
|
|
<MenuItem value={5}>沒有</MenuItem> |
|
|
|
|
|
</Select> |
|
|
|
|
|
</FormControl> |
|
|
|
|
|
</Stack> |
|
|
|
|
|
|
|
|
|
|
|
<Stack direction="row" spacing={1}> |
|
|
|
|
|
<TextField |
|
|
|
|
|
size="small" |
|
|
|
|
|
label={t("Depth")} |
|
|
|
|
|
type="number" |
|
|
|
|
|
value={editBasic.isDark} |
|
|
|
|
|
onChange={(e) => |
|
|
|
|
|
setEditBasic((p) => |
|
|
|
|
|
p ? { ...p, isDark: Number(e.target.value) } : p |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
inputProps={{ min: 1, max: 5, step: 1 }} |
|
|
|
|
|
/> |
|
|
|
|
|
<FormControl size="small" sx={{ minWidth: 140 }}> |
|
|
|
|
|
<InputLabel>{t("Float")}</InputLabel> |
|
|
|
|
|
<Select |
|
|
|
|
|
label={t("Float")} |
|
|
|
|
|
value={editBasic.isFloat} |
|
|
|
|
|
onChange={(e) => |
|
|
|
|
|
setEditBasic((p) => |
|
|
|
|
|
p ? { ...p, isFloat: Number(e.target.value) } : p, |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
> |
|
|
|
|
|
<MenuItem value={5}>沉</MenuItem> |
|
|
|
|
|
<MenuItem value={3}>浮</MenuItem> |
|
|
|
|
|
<MenuItem value={0}>不適用</MenuItem> |
|
|
|
|
|
</Select> |
|
|
|
|
|
</FormControl> |
|
|
|
|
|
<FormControl size="small" sx={{ minWidth: 140 }}> |
|
|
|
|
|
<InputLabel>{t("Density")}</InputLabel> |
|
|
|
|
|
<Select |
|
|
|
|
|
label={t("Density")} |
|
|
|
|
|
value={editBasic.isDense} |
|
|
|
|
|
onChange={(e) => |
|
|
|
|
|
setEditBasic((p) => |
|
|
|
|
|
p ? { ...p, isDense: Number(e.target.value) } : p, |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
> |
|
|
|
|
|
<MenuItem value={5}>淡</MenuItem> |
|
|
|
|
|
<MenuItem value={3}>濃</MenuItem> |
|
|
|
|
|
<MenuItem value={0}>不適用</MenuItem> |
|
|
|
|
|
</Select> |
|
|
|
|
|
</FormControl> |
|
|
|
|
|
</Stack> |
|
|
|
|
|
|
|
|
|
|
|
<Stack direction="row" spacing={1}> |
|
|
|
|
|
<FormControl size="small" sx={{ minWidth: 180 }}> |
|
|
|
|
|
<InputLabel>{t("Time Sequence")}</InputLabel> |
|
|
|
|
|
<Select |
|
|
|
|
|
label={t("Time Sequence")} |
|
|
|
|
|
value={editBasic.timeSequence} |
|
|
|
|
|
onChange={(e) => |
|
|
|
|
|
setEditBasic((p) => |
|
|
|
|
|
p |
|
|
|
|
|
? { ...p, timeSequence: Number(e.target.value) } |
|
|
|
|
|
: p, |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
> |
|
|
|
|
|
<MenuItem value={5}>上午</MenuItem> |
|
|
|
|
|
<MenuItem value={1}>下午</MenuItem> |
|
|
|
|
|
<MenuItem value={0}>不適用</MenuItem> |
|
|
|
|
|
</Select> |
|
|
|
|
|
</FormControl> |
|
|
|
|
|
<FormControl size="small" sx={{ minWidth: 180 }}> |
|
|
|
|
|
<InputLabel>{t("Complexity")}</InputLabel> |
|
|
|
|
|
<Select |
|
|
|
|
|
label={t("Complexity")} |
|
|
|
|
|
value={editBasic.complexity} |
|
|
|
|
|
onChange={(e) => |
|
|
|
|
|
setEditBasic((p) => |
|
|
|
|
|
p |
|
|
|
|
|
? { ...p, complexity: Number(e.target.value) } |
|
|
|
|
|
: p, |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
> |
|
|
|
|
|
<MenuItem value={10}>簡單</MenuItem> |
|
|
|
|
|
<MenuItem value={5}>中度</MenuItem> |
|
|
|
|
|
<MenuItem value={3}>複雜</MenuItem> |
|
|
|
|
|
<MenuItem value={0}>不適用</MenuItem> |
|
|
|
|
|
</Select> |
|
|
|
|
|
</FormControl> |
|
|
|
|
|
</Stack> |
|
|
|
|
|
|
|
|
|
|
|
<FormControlLabel |
|
|
|
|
|
control={ |
|
|
|
|
|
<Checkbox |
|
|
|
|
|
checked={editBasic.isDrink} |
|
|
|
|
|
onChange={(e) => |
|
|
|
|
|
setEditBasic((p) => |
|
|
|
|
|
p ? { ...p, isDrink: e.target.checked } : p |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
/> |
|
|
|
|
|
} |
|
|
|
|
|
label={t("Is Drink")} |
|
|
|
|
|
/> |
|
|
|
|
|
</Stack> |
|
|
|
|
|
)} |
|
|
</Paper> |
|
|
</Paper> |
|
|
{/* 材料列表 */} |
|
|
{/* 材料列表 */} |
|
|
<Paper variant="outlined" sx={{ p: 2 }}> |
|
|
<Paper variant="outlined" sx={{ p: 2 }}> |
|
|
@@ -243,36 +755,271 @@ const ImportBomDetailTab: React.FC = () => { |
|
|
<Typography variant="subtitle1" gutterBottom> |
|
|
<Typography variant="subtitle1" gutterBottom> |
|
|
{t("Process & Equipment")} |
|
|
{t("Process & Equipment")} |
|
|
</Typography> |
|
|
</Typography> |
|
|
|
|
|
{isEditing && ( |
|
|
|
|
|
<Box sx={{ mb: 1 }}> |
|
|
|
|
|
<Stack direction="row" spacing={1} flexWrap="wrap"> |
|
|
|
|
|
<FormControl size="small" sx={{ minWidth: 200 }}> |
|
|
|
|
|
<InputLabel>{t("Process Code")}</InputLabel> |
|
|
|
|
|
<Select |
|
|
|
|
|
label={t("Process Code")} |
|
|
|
|
|
value={processAddForm.processCode} |
|
|
|
|
|
onChange={(e) => |
|
|
|
|
|
setProcessAddForm((p) => ({ |
|
|
|
|
|
...p, |
|
|
|
|
|
processCode: String(e.target.value), |
|
|
|
|
|
})) |
|
|
|
|
|
} |
|
|
|
|
|
> |
|
|
|
|
|
{processCodeOptions.map((c) => ( |
|
|
|
|
|
<MenuItem key={c} value={c}> |
|
|
|
|
|
{c} |
|
|
|
|
|
</MenuItem> |
|
|
|
|
|
))} |
|
|
|
|
|
</Select> |
|
|
|
|
|
</FormControl> |
|
|
|
|
|
|
|
|
|
|
|
<FormControl size="small" sx={{ minWidth: 200 }}> |
|
|
|
|
|
<InputLabel>{t("Equipment Code")}</InputLabel> |
|
|
|
|
|
<Select |
|
|
|
|
|
label={t("Equipment Code")} |
|
|
|
|
|
value={processAddForm.equipmentCode} |
|
|
|
|
|
onChange={(e) => |
|
|
|
|
|
setProcessAddForm((p) => ({ |
|
|
|
|
|
...p, |
|
|
|
|
|
equipmentCode: String(e.target.value), |
|
|
|
|
|
})) |
|
|
|
|
|
} |
|
|
|
|
|
> |
|
|
|
|
|
<MenuItem value="">不適用</MenuItem> |
|
|
|
|
|
{equipmentCodeOptions.map((c) => ( |
|
|
|
|
|
<MenuItem key={c} value={c}> |
|
|
|
|
|
{c} |
|
|
|
|
|
</MenuItem> |
|
|
|
|
|
))} |
|
|
|
|
|
</Select> |
|
|
|
|
|
</FormControl> |
|
|
|
|
|
|
|
|
|
|
|
<TextField |
|
|
|
|
|
size="small" |
|
|
|
|
|
label={t("Process Description")} |
|
|
|
|
|
value={processAddForm.description} |
|
|
|
|
|
onChange={(e) => |
|
|
|
|
|
setProcessAddForm((p) => ({ |
|
|
|
|
|
...p, |
|
|
|
|
|
description: e.target.value, |
|
|
|
|
|
})) |
|
|
|
|
|
} |
|
|
|
|
|
/> |
|
|
|
|
|
|
|
|
|
|
|
<TextField |
|
|
|
|
|
size="small" |
|
|
|
|
|
label={t("Duration (Minutes)")} |
|
|
|
|
|
type="number" |
|
|
|
|
|
value={processAddForm.durationInMinute} |
|
|
|
|
|
onChange={(e) => |
|
|
|
|
|
setProcessAddForm((p) => ({ |
|
|
|
|
|
...p, |
|
|
|
|
|
durationInMinute: Number(e.target.value), |
|
|
|
|
|
})) |
|
|
|
|
|
} |
|
|
|
|
|
/> |
|
|
|
|
|
<TextField |
|
|
|
|
|
size="small" |
|
|
|
|
|
label={t("Prep Time (Minutes)")} |
|
|
|
|
|
type="number" |
|
|
|
|
|
value={processAddForm.prepTimeInMinute} |
|
|
|
|
|
onChange={(e) => |
|
|
|
|
|
setProcessAddForm((p) => ({ |
|
|
|
|
|
...p, |
|
|
|
|
|
prepTimeInMinute: Number(e.target.value), |
|
|
|
|
|
})) |
|
|
|
|
|
} |
|
|
|
|
|
/> |
|
|
|
|
|
<TextField |
|
|
|
|
|
size="small" |
|
|
|
|
|
label={t("Post Prod Time (Minutes)")} |
|
|
|
|
|
type="number" |
|
|
|
|
|
value={processAddForm.postProdTimeInMinute} |
|
|
|
|
|
onChange={(e) => |
|
|
|
|
|
setProcessAddForm((p) => ({ |
|
|
|
|
|
...p, |
|
|
|
|
|
postProdTimeInMinute: Number(e.target.value), |
|
|
|
|
|
})) |
|
|
|
|
|
} |
|
|
|
|
|
/> |
|
|
|
|
|
|
|
|
|
|
|
<Button |
|
|
|
|
|
size="small" |
|
|
|
|
|
startIcon={<AddIcon />} |
|
|
|
|
|
variant="contained" |
|
|
|
|
|
onClick={addProcessFromForm} |
|
|
|
|
|
> |
|
|
|
|
|
{t("Add")} |
|
|
|
|
|
</Button> |
|
|
|
|
|
</Stack> |
|
|
|
|
|
</Box> |
|
|
|
|
|
)} |
|
|
<Table size="small"> |
|
|
<Table size="small"> |
|
|
<TableHead> |
|
|
<TableHead> |
|
|
<TableRow> |
|
|
<TableRow> |
|
|
<TableCell> {t("Sequence")}</TableCell> |
|
|
<TableCell> {t("Sequence")}</TableCell> |
|
|
<TableCell> {t("Process Name")}</TableCell> |
|
|
<TableCell> {t("Process Name")}</TableCell> |
|
|
<TableCell> {t("Process Description")}</TableCell> |
|
|
<TableCell> {t("Process Description")}</TableCell> |
|
|
<TableCell> {t("Equipment Name")}</TableCell> |
|
|
|
|
|
|
|
|
<TableCell> {t("Process Code")}</TableCell> |
|
|
|
|
|
<TableCell> {t("Equipment Code")}</TableCell> |
|
|
<TableCell align="right"> {t("Duration (Minutes)")}</TableCell> |
|
|
<TableCell align="right"> {t("Duration (Minutes)")}</TableCell> |
|
|
<TableCell align="right"> {t("Prep Time (Minutes)")}</TableCell> |
|
|
<TableCell align="right"> {t("Prep Time (Minutes)")}</TableCell> |
|
|
<TableCell align="right"> {t("Post Prod Time (Minutes)")}</TableCell> |
|
|
<TableCell align="right"> {t("Post Prod Time (Minutes)")}</TableCell> |
|
|
|
|
|
{isEditing && ( |
|
|
|
|
|
<TableCell align="right">{t("Actions")}</TableCell> |
|
|
|
|
|
)} |
|
|
</TableRow> |
|
|
</TableRow> |
|
|
</TableHead> |
|
|
</TableHead> |
|
|
<TableBody> |
|
|
<TableBody> |
|
|
{detail.processes.map((p, i) => ( |
|
|
|
|
|
<TableRow key={i}> |
|
|
|
|
|
<TableCell>{p.seqNo}</TableCell> |
|
|
|
|
|
<TableCell>{p.processName}</TableCell> |
|
|
|
|
|
<TableCell>{p.processDescription}</TableCell> |
|
|
|
|
|
<TableCell>{p.equipmentName}</TableCell> |
|
|
|
|
|
<TableCell align="right"> |
|
|
|
|
|
{p.durationInMinute} |
|
|
|
|
|
</TableCell> |
|
|
|
|
|
<TableCell align="right"> |
|
|
|
|
|
{p.prepTimeInMinute} |
|
|
|
|
|
</TableCell> |
|
|
|
|
|
<TableCell align="right"> |
|
|
|
|
|
{p.postProdTimeInMinute} |
|
|
|
|
|
</TableCell> |
|
|
|
|
|
</TableRow> |
|
|
|
|
|
))} |
|
|
|
|
|
|
|
|
{isEditing |
|
|
|
|
|
? editProcesses.map((p) => ( |
|
|
|
|
|
<TableRow key={p.key}> |
|
|
|
|
|
<TableCell>{p.seqNo ?? "-"}</TableCell> |
|
|
|
|
|
<TableCell>{p.processName || "-"}</TableCell> |
|
|
|
|
|
<TableCell> |
|
|
|
|
|
<TextField |
|
|
|
|
|
size="small" |
|
|
|
|
|
value={p.description} |
|
|
|
|
|
onChange={(e) => |
|
|
|
|
|
setEditProcesses((prev) => |
|
|
|
|
|
prev.map((x) => |
|
|
|
|
|
x.key === p.key |
|
|
|
|
|
? { ...x, description: e.target.value } |
|
|
|
|
|
: x, |
|
|
|
|
|
), |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
/> |
|
|
|
|
|
</TableCell> |
|
|
|
|
|
<TableCell> |
|
|
|
|
|
<FormControl size="small" fullWidth> |
|
|
|
|
|
<Select |
|
|
|
|
|
value={p.processCode ?? ""} |
|
|
|
|
|
onChange={(e) => |
|
|
|
|
|
setEditProcesses((prev) => |
|
|
|
|
|
prev.map((x) => |
|
|
|
|
|
x.key === p.key |
|
|
|
|
|
? { |
|
|
|
|
|
...x, |
|
|
|
|
|
processCode: String(e.target.value), |
|
|
|
|
|
} |
|
|
|
|
|
: x, |
|
|
|
|
|
), |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
> |
|
|
|
|
|
<MenuItem value="">不適用</MenuItem> |
|
|
|
|
|
{processCodeOptions.map((c) => ( |
|
|
|
|
|
<MenuItem key={c} value={c}> |
|
|
|
|
|
{c} |
|
|
|
|
|
</MenuItem> |
|
|
|
|
|
))} |
|
|
|
|
|
</Select> |
|
|
|
|
|
</FormControl> |
|
|
|
|
|
</TableCell> |
|
|
|
|
|
<TableCell> |
|
|
|
|
|
<FormControl size="small" fullWidth> |
|
|
|
|
|
<Select |
|
|
|
|
|
value={p.equipmentCode ?? ""} |
|
|
|
|
|
onChange={(e) => |
|
|
|
|
|
setEditProcesses((prev) => |
|
|
|
|
|
prev.map((x) => |
|
|
|
|
|
x.key === p.key |
|
|
|
|
|
? { |
|
|
|
|
|
...x, |
|
|
|
|
|
equipmentCode: String(e.target.value), |
|
|
|
|
|
} |
|
|
|
|
|
: x, |
|
|
|
|
|
), |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
> |
|
|
|
|
|
<MenuItem value="">不適用</MenuItem> |
|
|
|
|
|
{equipmentCodeOptions.map((c) => ( |
|
|
|
|
|
<MenuItem key={c} value={c}> |
|
|
|
|
|
{c} |
|
|
|
|
|
</MenuItem> |
|
|
|
|
|
))} |
|
|
|
|
|
</Select> |
|
|
|
|
|
</FormControl> |
|
|
|
|
|
</TableCell> |
|
|
|
|
|
<TableCell align="right"> |
|
|
|
|
|
<TextField |
|
|
|
|
|
size="small" |
|
|
|
|
|
type="number" |
|
|
|
|
|
value={p.durationInMinute} |
|
|
|
|
|
onChange={(e) => |
|
|
|
|
|
setEditProcesses((prev) => |
|
|
|
|
|
prev.map((x) => |
|
|
|
|
|
x.key === p.key |
|
|
|
|
|
? { ...x, durationInMinute: Number(e.target.value) } |
|
|
|
|
|
: x, |
|
|
|
|
|
), |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
/> |
|
|
|
|
|
</TableCell> |
|
|
|
|
|
<TableCell align="right"> |
|
|
|
|
|
<TextField |
|
|
|
|
|
size="small" |
|
|
|
|
|
type="number" |
|
|
|
|
|
value={p.prepTimeInMinute} |
|
|
|
|
|
onChange={(e) => |
|
|
|
|
|
setEditProcesses((prev) => |
|
|
|
|
|
prev.map((x) => |
|
|
|
|
|
x.key === p.key |
|
|
|
|
|
? { ...x, prepTimeInMinute: Number(e.target.value) } |
|
|
|
|
|
: x, |
|
|
|
|
|
), |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
/> |
|
|
|
|
|
</TableCell> |
|
|
|
|
|
<TableCell align="right"> |
|
|
|
|
|
<TextField |
|
|
|
|
|
size="small" |
|
|
|
|
|
type="number" |
|
|
|
|
|
value={p.postProdTimeInMinute} |
|
|
|
|
|
onChange={(e) => |
|
|
|
|
|
setEditProcesses((prev) => |
|
|
|
|
|
prev.map((x) => |
|
|
|
|
|
x.key === p.key |
|
|
|
|
|
? { |
|
|
|
|
|
...x, |
|
|
|
|
|
postProdTimeInMinute: Number(e.target.value), |
|
|
|
|
|
} |
|
|
|
|
|
: x, |
|
|
|
|
|
), |
|
|
|
|
|
) |
|
|
|
|
|
} |
|
|
|
|
|
/> |
|
|
|
|
|
</TableCell> |
|
|
|
|
|
<TableCell align="right"> |
|
|
|
|
|
<IconButton size="small" onClick={() => deleteProcessRow(p.key)}> |
|
|
|
|
|
<DeleteIcon fontSize="small" /> |
|
|
|
|
|
</IconButton> |
|
|
|
|
|
</TableCell> |
|
|
|
|
|
</TableRow> |
|
|
|
|
|
)) |
|
|
|
|
|
: detail.processes.map((p, i) => ( |
|
|
|
|
|
<TableRow key={i}> |
|
|
|
|
|
<TableCell>{p.seqNo}</TableCell> |
|
|
|
|
|
<TableCell>{p.processName}</TableCell> |
|
|
|
|
|
<TableCell>{p.processDescription}</TableCell> |
|
|
|
|
|
<TableCell>{p.processCode ?? "-"}</TableCell> |
|
|
|
|
|
<TableCell>{p.equipmentCode ?? p.equipmentName}</TableCell> |
|
|
|
|
|
<TableCell align="right">{p.durationInMinute}</TableCell> |
|
|
|
|
|
<TableCell align="right">{p.prepTimeInMinute}</TableCell> |
|
|
|
|
|
<TableCell align="right"> |
|
|
|
|
|
{p.postProdTimeInMinute} |
|
|
|
|
|
</TableCell> |
|
|
|
|
|
</TableRow> |
|
|
|
|
|
))} |
|
|
</TableBody> |
|
|
</TableBody> |
|
|
</Table> |
|
|
</Table> |
|
|
</Paper> |
|
|
</Paper> |
|
|
|