Browse Source

update bom ui

reset-do-picking-order
CANCERYS\kw093 1 week ago
parent
commit
4b264a82a8
2 changed files with 139 additions and 31 deletions
  1. +1
    -1
      src/app/api/bom/index.ts
  2. +138
    -30
      src/components/ImportBom/ImportBomDetailTab.tsx

+ 1
- 1
src/app/api/bom/index.ts View File

@@ -83,7 +83,7 @@ export interface BomDetailResponse {
id: number;
itemCode?: string;
itemName?: string;
isDark?: boolean;
isDark?: number;
isFloat?: number;
isDense?: number;
isDrink?: boolean;


+ 138
- 30
src/components/ImportBom/ImportBomDetailTab.tsx View File

@@ -21,6 +21,8 @@ import type { BomCombo, BomDetailResponse } from "@/app/api/bom";
import { fetchBomComboClient, fetchBomDetailClient } from "@/app/api/bom/client";
import type { SelectChangeEvent } from "@mui/material/Select";
import { useTranslation } from "react-i18next";
import SearchBox, { Criterion } from "../SearchBox";
import { useMemo, useCallback } from "react";
const ImportBomDetailTab: React.FC = () => {
const { t } = useTranslation( "common" );
const [bomList, setBomList] = useState<BomCombo[]>([]);
@@ -28,7 +30,8 @@ const ImportBomDetailTab: React.FC = () => {
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);
useEffect(() => {
const loadList = async () => {
setLoadingList(true);
@@ -41,7 +44,91 @@ const ImportBomDetailTab: React.FC = () => {
};
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); // 初始顯示全部
}, [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 = b.label.toLowerCase();
const okCode = !code || label.includes(code);
const okName = !name || label.includes(name);
return okCode && okName;
});
setFilteredBoms(result);
// 如果只找到一個,直接載入明細
if (result.length === 1) {
const only = result[0];
setSelectedBomId(only.id);
setDetail(null);
setLoadingDetail(true);
fetchBomDetailClient(only.id)
.then((d) => setDetail(d))
.finally(() => setLoadingDetail(false));
}
},
[bomList],
);
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 handleChangeBom = async (event: SelectChangeEvent<number>) => {
const id = Number(event.target.value);
setSelectedBomId(id);
@@ -58,30 +145,22 @@ const ImportBomDetailTab: React.FC = () => {

return (
<Stack spacing={2}>
<FormControl size="small" sx={{ minWidth: 320 }}>
<InputLabel id="import-bom-detail-select-label">
{t("Please Select BOM")}
</InputLabel>
<Select
labelId="import-bom-detail-select-label"
label="請選擇 BOM"
value={selectedBomId}
onChange={handleChangeBom}
>
{loadingList && (
<MenuItem value="">
<CircularProgress size={20} sx={{ mr: 1 }} /> 載入中…
</MenuItem>
)}
{!loadingList &&
bomList.map((b) => (
<MenuItem key={b.id} value={b.id}>
{b.label}
</MenuItem>
))}
</Select>
</FormControl>

<SearchBox<BomSearchKey>
criteria={searchCriteria}
onSearch={handleSearchBom}
//onReset={handleResetBom}
/>
{currentBom && (
<Paper variant="outlined" sx={{ p: 1.5 }}>
<Typography variant="subtitle2">
CODE / NAME
</Typography>
<Typography variant="body2">
{currentBom.label} ({t("Output Quantity")} {currentBom.outputQty} {currentBom.outputQtyUom})
</Typography>
</Paper>
)}
{loadingDetail && (
<Typography variant="body2" color="text.secondary">
{t("Loading BOM Detail...")}
@@ -90,15 +169,44 @@ const ImportBomDetailTab: React.FC = () => {

{detail && (
<Stack spacing={2}>
<Typography variant="subtitle1">
{detail.itemCode} {detail.itemName}({t("Output Quantity")} {detail.outputQty}{" "}
{detail.outputQtyUom})
</Typography>
<Typography variant="subtitle1">
{detail.itemCode} {detail.itemName}
</Typography>

{/* Basic Info 列表 */}
<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 ?? "-"}
</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>
</Paper>
{/* 材料列表 */}
<Paper variant="outlined" sx={{ p: 2 }}>
<Typography variant="subtitle1" gutterBottom>
材料 (Bom Material)
{t("Bom Material")}
</Typography>
<Table size="small">
<TableHead>


Loading…
Cancel
Save