|
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219 |
- "use client";
-
- import React, { useEffect, useRef, useState } from "react";
- import {
- Box,
- Stack,
- Typography,
- FormControl,
- InputLabel,
- Select,
- MenuItem,
- CircularProgress,
- Paper,
- Table,
- TableHead,
- TableRow,
- TableCell,
- TableBody,
- Button,
- TextField,
- Checkbox,
- FormControlLabel,
- IconButton,
- } from "@mui/material";
- import type { BomCombo, BomDetailResponse } from "@/app/api/bom";
- import {
- editBomClient,
- fetchBomComboClient,
- fetchBomDetailClient,
- fetchAllEquipmentsMasterClient,
- fetchAllProcessesMasterClient,
- type EquipmentMasterRow,
- type ProcessMasterRow,
- } from "@/app/api/bom/client";
- import { useTranslation } from "react-i18next";
- import SearchBox, { Criterion } from "../SearchBox";
- 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";
-
- /** 以 description + "-" + name 對應 code,或同一筆設備的 description+name。 */
- function resolveEquipmentCode(
- list: EquipmentMasterRow[],
- description: string,
- name: string,
- ): string | null {
- const d = description.trim();
- const n = name.trim();
- if (!d && !n) return null;
- if (!d || !n) return null;
- const composite = `${d}-${n}`;
- const byCode = list.find((e) => e.code === composite);
- if (byCode) return byCode.code;
- const byPair = list.find(
- (e) => e.description === d && e.name === n,
- );
- return byPair?.code ?? null;
- }
-
- const ImportBomDetailTab: React.FC = () => {
- const { t } = useTranslation( "common" );
- const [bomList, setBomList] = useState<BomCombo[]>([]);
- const [selectedBomId, setSelectedBomId] = useState<number | "">("");
- const [detail, setDetail] = useState<BomDetailResponse | null>(null);
- const [loadingList, setLoadingList] = useState(false);
- const [loadingDetail, setLoadingDetail] = useState(false);
- const [filteredBoms, setFilteredBoms] = useState<BomCombo[]>([])
- const [currentBom, setCurrentBom] = useState<BomCombo | null>(null);
- const loadDetailInFlightRef = useRef(false);
-
- 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;
- /** 設備主檔 description(下拉),與 equipmentName 一併解析為 equipment.code */
- equipmentDescription: string;
- equipmentName: 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[]>([]);
-
- const [equipmentMasterList, setEquipmentMasterList] = useState<
- EquipmentMasterRow[]
- >([]);
- const [processMasterList, setProcessMasterList] = useState<
- ProcessMasterRow[]
- >([]);
- const [editMasterLoading, setEditMasterLoading] = useState(false);
-
- // Process add form (uses dropdown selections from master tables).
- const [processAddForm, setProcessAddForm] = useState<{
- processCode: string;
- equipmentDescription: string;
- equipmentName: string;
- description: string;
- durationInMinute: number;
- prepTimeInMinute: number;
- postProdTimeInMinute: number;
- }>({
- processCode: "",
- equipmentDescription: "",
- equipmentName: "",
- description: "",
- durationInMinute: 0,
- prepTimeInMinute: 0,
- postProdTimeInMinute: 0,
- });
-
- const processCodeOptions = useMemo(() => {
- const codes = new Set<string>();
- processMasterList.forEach((p) => {
- if (p.code) codes.add(p.code);
- });
- return Array.from(codes).sort();
- }, [processMasterList]);
-
- const equipmentDescriptionOptions = useMemo(() => {
- const s = new Set<string>();
- equipmentMasterList.forEach((e) => {
- if (e.description) s.add(e.description);
- });
- return Array.from(s).sort();
- }, [equipmentMasterList]);
-
- const equipmentNameOptions = useMemo(() => {
- const s = new Set<string>();
- equipmentMasterList.forEach((e) => {
- if (e.name) s.add(e.name);
- });
- return Array.from(s).sort();
- }, [equipmentMasterList]);
-
- useEffect(() => {
- const loadList = async () => {
- setLoadingList(true);
- try {
- const list = await fetchBomComboClient();
- setBomList(list);
- } finally {
- setLoadingList(false);
- }
- };
- loadList();
- }, []);
- type BomSearchKey = "code" | "name";
-
- const searchCriteria: Criterion<BomSearchKey>[] = useMemo(
- () => [
- { label: t("Code"), paramName: "code", type: "text" },
- { label: t("Name"), paramName: "name", type: "text" },
- ],
- [t],
- );
- useEffect(() => {
- setFilteredBoms([]);
- }, [bomList]);
- const loadBomDetail = useCallback(
- async (id: number) => {
- if (!id || loadDetailInFlightRef.current) return;
- loadDetailInFlightRef.current = true;
- setSelectedBomId(id);
- setCurrentBom(bomList.find((b) => b.id === id) ?? null);
- setDetail(null);
- setLoadingDetail(true);
- try {
- const d = await fetchBomDetailClient(id);
- setDetail(d);
- } finally {
- setLoadingDetail(false);
- loadDetailInFlightRef.current = false;
- }
- },
- [bomList],
- );
-
- const handleSearchBom = useCallback(
- (inputs: Record<BomSearchKey | `${BomSearchKey}To`, string>) => {
- const code = (inputs.code ?? "").trim().toLowerCase();
- const name = (inputs.name ?? "").trim().toLowerCase();
-
- const result = bomList.filter((b) => {
- const label = String(b.label ?? "").toLowerCase();
- const okCode = !code || label.includes(code);
- const okName = !name || label.includes(name);
- return okCode && okName;
- });
-
- setFilteredBoms(result);
-
- // 如果只找到一個,直接載入明細
- if (result.length === 1) {
- void loadBomDetail(result[0].id);
- } else {
- setSelectedBomId("");
- setCurrentBom(null);
- setDetail(null);
- }
- },
- [bomList, loadBomDetail],
- );
- const renderAllergic = (v?: number) => {
- if (v === 0) return "有";
- if (v === 5) return "沒有";
- return "-";
- };
-
- const renderIsFloat = (v?: number) => {
- if (v === 5) return "沉";
- if (v === 3) return "浮";
- if (v === 0) return "不適用";
- return "-";
- };
-
- const renderIsDense = (v?: number) => {
- if (v === 5) return "淡";
- if (v === 3) return "濃";
- if (v === 0) return "不適用";
- return "-";
- };
-
- const renderTimeSequence = (v?: number) => {
- if (v === 5) return "上午";
- if (v === 1) return "下午";
- if (v === 0) return "不適用";
- return "-";
- };
- const renderType = (v?: string) => {
- if (v === "FG") return "成品";
- if (v === "WIP") return "半成品";
- return "-";
- };
-
- const renderComplexity = (v?: number) => {
- if (v === 10) return "簡單";
- if (v === 5) return "中度";
- if (v === 3) return "複雜";
- if (v === 0) return "不適用";
- return "-";
- };
- /*
- const handleResetBom = useCallback(() => {
- setFilteredBoms(bomList);
- setSelectedBomId("");
- setDetail(null);
- }, [bomList]);
- */
- const genKey = () => Math.random().toString(36).slice(2);
-
- const startEdit = useCallback(async () => {
- if (!detail) return;
-
- setEditError(null);
- setEditMasterLoading(true);
- try {
- const [equipments, processes] = await Promise.all([
- fetchAllEquipmentsMasterClient(),
- fetchAllProcessesMasterClient(),
- ]);
- setEquipmentMasterList(equipments);
- setProcessMasterList(processes);
-
- 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) => {
- const code = (p.equipmentCode ?? "").trim();
- const eq = code
- ? equipments.find((e) => e.code === code)
- : undefined;
- return {
- key: genKey(),
- id: undefined,
- seqNo: p.seqNo,
- processCode: p.processCode ?? "",
- processName: p.processName,
- description: p.processDescription ?? "",
- equipmentDescription: eq?.description ?? "",
- equipmentName: eq?.name ?? "",
- durationInMinute: p.durationInMinute ?? 0,
- prepTimeInMinute: p.prepTimeInMinute ?? 0,
- postProdTimeInMinute: p.postProdTimeInMinute ?? 0,
- };
- }),
- );
-
- setIsEditing(true);
- } catch (e: unknown) {
- const msg =
- e && typeof e === "object" && "message" in e
- ? String((e as { message?: string }).message)
- : "載入製程/設備主檔失敗";
- setEditError(msg);
- } finally {
- setEditMasterLoading(false);
- }
- }, [detail]);
-
- const cancelEdit = useCallback(() => {
- setIsEditing(false);
- setEditLoading(false);
- setEditError(null);
- setEditBasic(null);
- setEditMaterials([]);
- setEditProcesses([]);
- setProcessAddForm({
- processCode: "",
- equipmentDescription: "",
- equipmentName: "",
- description: "",
- durationInMinute: 0,
- prepTimeInMinute: 0,
- postProdTimeInMinute: 0,
- });
- setEquipmentMasterList([]);
- setProcessMasterList([]);
- }, []);
-
- 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: "",
- equipmentDescription: "",
- equipmentName: "",
- durationInMinute: 0,
- prepTimeInMinute: 0,
- postProdTimeInMinute: 0,
- },
- ]);
- }, []);
-
- const addProcessFromForm = useCallback(() => {
- const pCode = processAddForm.processCode.trim();
- if (!pCode) {
- setEditError("請先選擇工序 Process Code");
- return;
- }
-
- const ed = processAddForm.equipmentDescription.trim();
- const en = processAddForm.equipmentName.trim();
- if ((ed && !en) || (!ed && en)) {
- setEditError("設備描述與名稱需同時選取,或同時留空(不適用)");
- return;
- }
- if (ed && en) {
- const resolved = resolveEquipmentCode(equipmentMasterList, ed, en);
- if (!resolved) {
- setEditError(
- `設備組合「${ed}-${en}」在主檔中找不到對應設備代碼,請確認後再試`,
- );
- return;
- }
- }
-
- setEditProcesses((prev) => [
- ...prev,
- {
- key: genKey(),
- seqNo: undefined,
- processCode: pCode,
- processName: "",
- description: processAddForm.description ?? "",
- equipmentDescription: ed,
- equipmentName: en,
- durationInMinute: processAddForm.durationInMinute ?? 0,
- prepTimeInMinute: processAddForm.prepTimeInMinute ?? 0,
- postProdTimeInMinute: processAddForm.postProdTimeInMinute ?? 0,
- },
- ]);
-
- setProcessAddForm({
- processCode: "",
- equipmentDescription: "",
- equipmentName: "",
- description: "",
- durationInMinute: 0,
- prepTimeInMinute: 0,
- postProdTimeInMinute: 0,
- });
- setEditError(null);
- }, [processAddForm, equipmentMasterList]);
-
- 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 ed = p.equipmentDescription.trim();
- const en = p.equipmentName.trim();
- if ((ed && !en) || (!ed && en)) {
- throw new Error("各製程行的設備描述與名稱需同時填寫或同時留空");
- }
- if (ed && en) {
- const resolved = resolveEquipmentCode(equipmentMasterList, ed, en);
- if (!resolved) {
- throw new Error(
- `設備「${ed}-${en}」在主檔中無對應設備代碼,請修正後再儲存`,
- );
- }
- }
- }
-
- 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) => {
- const ed = p.equipmentDescription.trim();
- const en = p.equipmentName.trim();
- const equipmentCode =
- ed && en
- ? resolveEquipmentCode(equipmentMasterList, ed, en) ?? undefined
- : undefined;
- return {
- id: p.id,
- seqNo: p.seqNo,
- processCode: p.processCode?.trim() || undefined,
- equipmentCode,
- 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, equipmentMasterList]);
-
- return (
- <Stack spacing={2}>
- <SearchBox<BomSearchKey>
- criteria={searchCriteria}
- onSearch={handleSearchBom}
- //onReset={handleResetBom}
- />
-
-
- {filteredBoms.length > 1 && (
- <Paper variant="outlined" sx={{ p: 1.5 }}>
- <Typography variant="subtitle2" sx={{ mb: 1 }}>
- 找到多筆 BOM,請選擇一筆載入明細
- </Typography>
- <Stack direction="row" spacing={1} flexWrap="wrap">
- {filteredBoms.map((b) => (
- <Button
- key={b.id}
- size="small"
- variant={selectedBomId === b.id ? "contained" : "outlined"}
- disabled={loadingDetail}
- onClick={() => void loadBomDetail(b.id)}
- >
- {String(b.label ?? b.id)} ({renderType(b.description)})
- </Button>
- ))}
- </Stack>
- </Paper>
- )}
- {loadingDetail && (
- <Typography variant="body2" color="text.secondary">
- {t("Loading BOM Detail...")}
- </Typography>
- )}
-
- {detail && (
- <Stack spacing={2}>
- <Typography variant="subtitle1">
- {detail.itemCode} {detail.itemName}
- </Typography>
-
- {/* Basic Info 列表 */}
- <Paper variant="outlined" sx={{ p: 2 }}>
- <Stack
- direction="row"
- alignItems="center"
- justifyContent="space-between"
- sx={{ mb: 1 }}
- >
- <Typography variant="subtitle1" gutterBottom>
- {t("Basic Info")}
- </Typography>
-
- {!isEditing ? (
- <Button
- size="small"
- startIcon={
- editMasterLoading ? (
- <CircularProgress size={16} />
- ) : (
- <EditIcon />
- )
- }
- variant="outlined"
- onClick={() => void startEdit()}
- disabled={editMasterLoading}
- >
- {editMasterLoading ? t("Loading...") : 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>
-
- {editError && (
- <Typography variant="body2" color="error">
- {editError}
- </Typography>
- )}
-
- {!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 variant="outlined" sx={{ p: 2 }}>
- <Typography variant="subtitle1" gutterBottom>
- {t("Bom Material")}
- </Typography>
- <Table size="small">
- <TableHead>
- <TableRow>
- <TableCell> {t("Item Code")}</TableCell>
- <TableCell> {t("Item Name")}</TableCell>
- <TableCell align="right"> {t("Base Qty")}</TableCell>
- <TableCell> {t("Base UOM")}</TableCell>
- <TableCell align="right"> {t("Stock Qty")}</TableCell>
- <TableCell> {t("Stock UOM")}</TableCell>
- <TableCell align="right"> {t("Sales Qty")}</TableCell>
- <TableCell> {t("Sales UOM")}</TableCell>
- </TableRow>
- </TableHead>
- <TableBody>
- {detail.materials.map((m, i) => (
- <TableRow key={i}>
- <TableCell>{m.itemCode}</TableCell>
- <TableCell>{m.itemName}</TableCell>
- <TableCell align="right">{m.baseQty}</TableCell>
- <TableCell>{m.baseUom}</TableCell>
- <TableCell align="right">{m.stockQty}</TableCell>
- <TableCell>{m.stockUom}</TableCell>
- <TableCell align="right">{m.salesQty}</TableCell>
- <TableCell>{m.salesUom}</TableCell>
- </TableRow>
- ))}
- </TableBody>
- </Table>
- </Paper>
-
- {/* 製程 + 設備列表 */}
- <Paper variant="outlined" sx={{ p: 2 }}>
- <Typography variant="subtitle1" gutterBottom>
- {t("Process & Equipment")}
- </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),
- }))
- }
- >
- <MenuItem value="">
- <em>請選擇</em>
- </MenuItem>
- {processCodeOptions.map((c) => (
- <MenuItem key={c} value={c}>
- {c}
- </MenuItem>
- ))}
- </Select>
- </FormControl>
-
- <FormControl size="small" sx={{ minWidth: 200 }}>
- <InputLabel>設備說明</InputLabel>
- <Select
- label="設備說明"
- value={processAddForm.equipmentDescription}
- onChange={(e) =>
- setProcessAddForm((p) => ({
- ...p,
- equipmentDescription: String(e.target.value),
- }))
- }
- >
- <MenuItem value="">不適用</MenuItem>
- {equipmentDescriptionOptions.map((c) => (
- <MenuItem key={c} value={c}>
- {c}
- </MenuItem>
- ))}
- </Select>
- </FormControl>
-
- <FormControl size="small" sx={{ minWidth: 200 }}>
- <InputLabel>設備名稱</InputLabel>
- <Select
- label="設備名稱"
- value={processAddForm.equipmentName}
- onChange={(e) =>
- setProcessAddForm((p) => ({
- ...p,
- equipmentName: String(e.target.value),
- }))
- }
- >
- <MenuItem value="">不適用</MenuItem>
- {equipmentNameOptions.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">
- <TableHead>
- <TableRow>
- <TableCell> {t("Sequence")}</TableCell>
- <TableCell> {t("Process Name")}</TableCell>
- <TableCell> {t("Process Description")}</TableCell>
- <TableCell> {t("Process Code")}</TableCell>
- <TableCell>設備(說明/名稱)</TableCell>
- <TableCell align="right"> {t("Duration (Minutes)")}</TableCell>
- <TableCell align="right"> {t("Prep Time (Minutes)")}</TableCell>
- <TableCell align="right"> {t("Post Prod Time (Minutes)")}</TableCell>
- {isEditing && (
- <TableCell align="right">{t("Actions")}</TableCell>
- )}
- </TableRow>
- </TableHead>
- <TableBody>
- {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>
- <Stack direction="row" spacing={0.5} flexWrap="wrap">
- <FormControl size="small" sx={{ minWidth: 140 }}>
- <Select
- displayEmpty
- value={p.equipmentDescription}
- onChange={(e) =>
- setEditProcesses((prev) =>
- prev.map((x) =>
- x.key === p.key
- ? {
- ...x,
- equipmentDescription: String(
- e.target.value,
- ),
- }
- : x,
- ),
- )
- }
- >
- <MenuItem value="">不適用</MenuItem>
- {equipmentDescriptionOptions.map((c) => (
- <MenuItem key={c} value={c}>
- {c}
- </MenuItem>
- ))}
- </Select>
- </FormControl>
- <FormControl size="small" sx={{ minWidth: 140 }}>
- <Select
- displayEmpty
- value={p.equipmentName}
- onChange={(e) =>
- setEditProcesses((prev) =>
- prev.map((x) =>
- x.key === p.key
- ? {
- ...x,
- equipmentName: String(e.target.value),
- }
- : x,
- ),
- )
- }
- >
- <MenuItem value="">不適用</MenuItem>
- {equipmentNameOptions.map((c) => (
- <MenuItem key={c} value={c}>
- {c}
- </MenuItem>
- ))}
- </Select>
- </FormControl>
- </Stack>
- </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>
- </Table>
- </Paper>
- </Stack>
- )}
- </Stack>
- );
- };
-
- export default ImportBomDetailTab;
|