ソースを参照

demo bom excel

bomUpdateTest
CANCERYS\kw093 2週間前
コミット
a0515e4fbb
5個のファイルの変更77行の追加3行の削除
  1. +14
    -0
      src/app/api/bom/client.ts
  2. +26
    -2
      src/components/ImportBom/BomVersionControlBar.tsx
  3. +33
    -1
      src/components/ImportBom/ImportBomDetailTab.tsx
  4. +2
    -0
      src/i18n/en/importBom.json
  5. +2
    -0
      src/i18n/zh/importBom.json

+ 14
- 0
src/app/api/bom/client.ts ファイルの表示

@@ -182,6 +182,20 @@ export async function fetchBomComboClient(options?: {
return response.data; return response.data;
} }


export async function downloadBomDetailExcelClient(
id: number,
fileName: string,
): Promise<{ blob: Blob; fileName: string }> {
const response = await axiosInstance.get(
`${NEXT_PUBLIC_API_URL}/bom/${id}/export-excel`,
{ responseType: "blob" },
);
return {
blob: response.data as Blob,
fileName,
};
}

export async function editBomClient( export async function editBomClient(
id: number, id: number,
request: EditBomRequest, request: EditBomRequest,


+ 26
- 2
src/components/ImportBom/BomVersionControlBar.tsx ファイルの表示

@@ -14,6 +14,7 @@ import {
Typography, Typography,
} from "@mui/material"; } from "@mui/material";
import SaveIcon from "@mui/icons-material/Save"; import SaveIcon from "@mui/icons-material/Save";
import FileDownloadIcon from "@mui/icons-material/FileDownload";
import type { BomVersionSummary } from "@/app/api/bom"; import type { BomVersionSummary } from "@/app/api/bom";
import { fetchBomVersionsClient } from "@/app/api/bom/client"; import { fetchBomVersionsClient } from "@/app/api/bom/client";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@@ -26,12 +27,15 @@ type Props = {
compareMode: boolean; compareMode: boolean;
compareToId: number | ""; compareToId: number | "";
saving: boolean; saving: boolean;
exporting?: boolean;
versionsRefreshKey?: number; versionsRefreshKey?: number;
saveDisabled: boolean; saveDisabled: boolean;
exportDisabled?: boolean;
saveError?: string | null; saveError?: string | null;
onVersionChange: (id: number) => void; onVersionChange: (id: number) => void;
onToggleCompare: () => void; onToggleCompare: () => void;
onCompareToChange: (id: number) => void; onCompareToChange: (id: number) => void;
onExport?: () => void;
onSave: () => void; onSave: () => void;
}; };


@@ -63,12 +67,15 @@ export const BomVersionControlBar: React.FC<Props> = ({
compareMode, compareMode,
compareToId, compareToId,
saving, saving,
exporting = false,
versionsRefreshKey = 0, versionsRefreshKey = 0,
saveDisabled, saveDisabled,
exportDisabled = false,
saveError, saveError,
onVersionChange, onVersionChange,
onToggleCompare, onToggleCompare,
onCompareToChange, onCompareToChange,
onExport,
onSave, onSave,
}) => { }) => {
const { t } = useTranslation("importBom"); const { t } = useTranslation("importBom");
@@ -94,7 +101,7 @@ export const BomVersionControlBar: React.FC<Props> = ({
}, [bomCode, bomKind, versionsRefreshKey]); }, [bomCode, bomKind, versionsRefreshKey]);


const showCompareButton = versions.length >= 2; const showCompareButton = versions.length >= 2;
const controlsDisabled = loadingVersions || saving;
const controlsDisabled = loadingVersions || saving || exporting;
const comparingTwo = const comparingTwo =
compareMode && compareMode &&
versionId !== "" && versionId !== "" &&
@@ -189,6 +196,23 @@ export const BomVersionControlBar: React.FC<Props> = ({
</Stack> </Stack>


<Stack direction="row" spacing={1} alignItems="center" flexWrap="wrap"> <Stack direction="row" spacing={1} alignItems="center" flexWrap="wrap">
{onExport && (
<Button
size="small"
variant="outlined"
startIcon={
exporting ? (
<CircularProgress size={16} color="inherit" />
) : (
<FileDownloadIcon />
)
}
disabled={saving || exporting || exportDisabled}
onClick={onExport}
>
{exporting ? t("Downloading...") : t("Export Excel")}
</Button>
)}
<Button <Button
size="small" size="small"
variant="contained" variant="contained"
@@ -199,7 +223,7 @@ export const BomVersionControlBar: React.FC<Props> = ({
<SaveIcon /> <SaveIcon />
) )
} }
disabled={saving || saveDisabled}
disabled={saving || exporting || saveDisabled}
onClick={onSave} onClick={onSave}
> >
{saving ? t("Saving...") : t("Save")} {saving ? t("Saving...") : t("Save")}


+ 33
- 1
src/components/ImportBom/ImportBomDetailTab.tsx ファイルの表示

@@ -32,6 +32,7 @@ import {
fetchBomDetailClient, fetchBomDetailClient,
fetchAllEquipmentsMasterClient, fetchAllEquipmentsMasterClient,
fetchAllProcessesMasterClient, fetchAllProcessesMasterClient,
downloadBomDetailExcelClient,
type EquipmentMasterRow, type EquipmentMasterRow,
type ProcessMasterRow, type ProcessMasterRow,
} from "@/app/api/bom/client"; } from "@/app/api/bom/client";
@@ -100,6 +101,7 @@ const ImportBomDetailTab: React.FC = () => {
const [currentBom, setCurrentBom] = useState<BomCombo | null>(null); const [currentBom, setCurrentBom] = useState<BomCombo | null>(null);
const loadDetailInFlightRef = useRef(false); const loadDetailInFlightRef = useRef(false);
const saveInFlightRef = useRef(false); const saveInFlightRef = useRef(false);
const exportInFlightRef = useRef(false);
const skipAutoSearchRef = useRef(false); const skipAutoSearchRef = useRef(false);
const detailIdRef = useRef<number | null>(null); const detailIdRef = useRef<number | null>(null);
const lastSearchInputsRef = useRef<BomSearchInputs>(EMPTY_BOM_SEARCH_INPUTS); const lastSearchInputsRef = useRef<BomSearchInputs>(EMPTY_BOM_SEARCH_INPUTS);
@@ -164,6 +166,7 @@ const ImportBomDetailTab: React.FC = () => {
>([]); >([]);
const [editMasterLoading, setEditMasterLoading] = useState(false); const [editMasterLoading, setEditMasterLoading] = useState(false);
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const [exporting, setExporting] = useState(false);
const [saveError, setSaveError] = useState<string | null>(null); const [saveError, setSaveError] = useState<string | null>(null);
const [versionId, setVersionId] = useState<number | "">(""); const [versionId, setVersionId] = useState<number | "">("");
const [compareMode, setCompareMode] = useState(false); const [compareMode, setCompareMode] = useState(false);
@@ -549,6 +552,32 @@ const ImportBomDetailTab: React.FC = () => {
unresolvedMaterialIssueCount, unresolvedMaterialIssueCount,
]); ]);


const handleExportExcel = useCallback(async () => {
if (typeof versionId !== "number" || exportInFlightRef.current) return;
exportInFlightRef.current = true;
setExporting(true);
setSaveError(null);
try {
const code = (detail?.itemCode ?? currentBom?.code ?? "BOM").trim() || "BOM";
const rev = detail?.revisionNo ?? 1;
const { blob, fileName } = await downloadBomDetailExcelClient(
versionId,
`${code}_V${rev}.xlsx`,
);
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = fileName;
a.click();
URL.revokeObjectURL(url);
} catch (e: unknown) {
setSaveError(e instanceof Error ? e.message : t("Export excel failed"));
} finally {
setExporting(false);
exportInFlightRef.current = false;
}
}, [currentBom?.code, detail?.itemCode, detail?.revisionNo, t, versionId]);

const handleToggleCompare = useCallback(async () => { const handleToggleCompare = useCallback(async () => {
if (compareMode) { if (compareMode) {
setCompareMode(false); setCompareMode(false);
@@ -866,7 +895,7 @@ const ImportBomDetailTab: React.FC = () => {
</Paper> </Paper>
)} )}
{detail && ( {detail && (
<Stack spacing={2} sx={{ pointerEvents: saving ? "none" : "auto" }}>
<Stack spacing={2} sx={{ pointerEvents: saving || exporting ? "none" : "auto" }}>
<Typography variant="subtitle1"> <Typography variant="subtitle1">
{detail.itemCode} {detail.itemName} {detail.itemCode} {detail.itemName}
</Typography> </Typography>
@@ -879,8 +908,10 @@ const ImportBomDetailTab: React.FC = () => {
compareMode={compareMode} compareMode={compareMode}
compareToId={compareToId} compareToId={compareToId}
saving={saving} saving={saving}
exporting={exporting}
versionsRefreshKey={versionsRefreshKey} versionsRefreshKey={versionsRefreshKey}
saveDisabled={effectiveSaveDisabled} saveDisabled={effectiveSaveDisabled}
exportDisabled={typeof versionId !== "number"}
saveError={ saveError={
blockedByUnresolvedMaterialIssue blockedByUnresolvedMaterialIssue
? unresolvedMaterialIssueMessage ? unresolvedMaterialIssueMessage
@@ -892,6 +923,7 @@ const ImportBomDetailTab: React.FC = () => {
}} }}
onToggleCompare={() => void handleToggleCompare()} onToggleCompare={() => void handleToggleCompare()}
onCompareToChange={setCompareToId} onCompareToChange={setCompareToId}
onExport={() => void handleExportExcel()}
onSave={() => void handleSave()} onSave={() => void handleSave()}
/> />
)} )}


+ 2
- 0
src/i18n/en/importBom.json ファイルの表示

@@ -42,6 +42,8 @@
"Download corrected excel": "Download corrected Excel", "Download corrected excel": "Download corrected Excel",
"Downloading...": "Downloading…", "Downloading...": "Downloading…",
"Download corrected excel failed": "Failed to download corrected Excel", "Download corrected excel failed": "Failed to download corrected Excel",
"Export Excel": "Export Excel",
"Export excel failed": "Failed to export Excel",
"Confirm import": "Confirm import", "Confirm import": "Confirm import",
"Download issue log": "Download issue log Excel", "Download issue log": "Download issue log Excel",
"Import completed": "Import completed", "Import completed": "Import completed",


+ 2
- 0
src/i18n/zh/importBom.json ファイルの表示

@@ -42,6 +42,8 @@
"Download corrected excel": "下載修正版 Excel", "Download corrected excel": "下載修正版 Excel",
"Downloading...": "下載中…", "Downloading...": "下載中…",
"Download corrected excel failed": "下載修正版 Excel 失敗", "Download corrected excel failed": "下載修正版 Excel 失敗",
"Export Excel": "匯出 Excel",
"Export excel failed": "匯出 Excel 失敗",
"Confirm import": "確認匯入", "Confirm import": "確認匯入",
"Download issue log": "下載檢查結果 Excel", "Download issue log": "下載檢查結果 Excel",
"Import completed": "匯入完成", "Import completed": "匯入完成",


読み込み中…
キャンセル
保存