"use client" import * as React from "react" import { Upload } from "lucide-react" import { toast } from "sonner" import * as ExcelJS from 'exceljs' import { Button } from "@/components/ui/button" import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog" import { Progress } from "@/components/ui/progress" import { processFileImport } from "./ship/import-item-handler" import { processTopFileImport } from "./top/import-item-handler" import { processHullFileImport } from "./hull/import-item-handler" import { decryptWithServerAction } from "@/components/drm/drmUtils" // 선박 아이템 타입 type ItemType = "ship" | "top" | "hull"; const ITEM_TYPE_NAMES = { ship: "조선 아이템", top: "해양 TOP 아이템", hull: "해양 HULL 아이템", }; interface ImportItemButtonProps { itemType: ItemType; onSuccess?: () => void; } export function ImportItemButton({ itemType, onSuccess }: ImportItemButtonProps) { const [open, setOpen] = React.useState(false); const [file, setFile] = React.useState(null); const [isUploading, setIsUploading] = React.useState(false); const [progress, setProgress] = React.useState(0); const [error, setError] = React.useState(null); const fileInputRef = React.useRef(null); // 파일 선택 처리 const handleFileChange = (e: React.ChangeEvent) => { const selectedFile = e.target.files?.[0]; if (!selectedFile) return; if (!selectedFile.name.endsWith('.xlsx') && !selectedFile.name.endsWith('.xls')) { setError("Excel 파일(.xlsx 또는 .xls)만 가능합니다."); return; } setFile(selectedFile); setError(null); }; // 데이터 가져오기 처리 const handleImport = async () => { if (!file) { setError("가져올 파일을 선택해주세요."); return; } try { setIsUploading(true); setProgress(0); setError(null); // DRM 복호화 처리 - 서버 액션 직접 호출 let arrayBuffer: ArrayBuffer; try { setProgress(10); toast.info("파일 복호화 중..."); arrayBuffer = await decryptWithServerAction(file); setProgress(30); } catch (decryptError) { console.error("파일 복호화 실패, 원본 파일 사용:", decryptError); toast.warning("파일 복호화에 실패하여 원본 파일을 사용합니다."); // 복호화 실패 시 원본 파일 사용 arrayBuffer = await file.arrayBuffer(); } // ExcelJS 워크북 로드 const workbook = new ExcelJS.Workbook(); await workbook.xlsx.load(arrayBuffer); // 첫 번째 워크시트 가져오기 const worksheet = workbook.worksheets[0]; if (!worksheet) { throw new Error("Excel 파일에 워크시트가 없습니다."); } // 헤더 행 찾기 let headerRowIndex = 1; let headerRow: ExcelJS.Row | undefined; let headerValues: (string | null)[] = []; worksheet.eachRow((row, rowNumber) => { const values = row.values as (string | null)[]; if (!headerRow && values.some(v => v === "아이템 코드" || v === "itemCode" || v === "item_code")) { headerRowIndex = rowNumber; headerRow = row; headerValues = [...values]; } }); if (!headerRow) { throw new Error("Excel 파일에서 헤더 행을 찾을 수 없습니다."); } // 헤더를 기반으로 인덱스 매핑 생성 const headerMapping: Record = {}; headerValues.forEach((value, index) => { if (typeof value === 'string') { headerMapping[value] = index; } }); // 필수 헤더 확인 (타입별 구분) const requiredHeaders: string[] = ["아이템 코드", "기능(공종)"]; const alternativeHeaders = { "아이템 코드": ["itemCode", "item_code"], "기능(공종)": ["workType"], "자재명": ["itemList"], "자재명(상세)": ["subItemList"] }; // 헤더 매핑 확인 (대체 이름 포함) const missingHeaders = requiredHeaders.filter(header => { const alternatives = alternativeHeaders[header as keyof typeof alternativeHeaders] || []; return !(header in headerMapping) && !alternatives.some(alt => alt in headerMapping); }); if (missingHeaders.length > 0) { throw new Error(`다음 필수 헤더가 누락되었습니다: ${missingHeaders.join(", ")}`); } // 데이터 행 추출 (헤더 이후 행부터) const dataRows: Record[] = []; worksheet.eachRow((row, rowNumber) => { if (rowNumber > headerRowIndex) { const rowData: Record = {}; const values = row.values as (string | null | undefined)[]; // 헤더 매핑에 따라 데이터 추출 Object.entries(headerMapping).forEach(([header, index]) => { rowData[header] = values[index] || ""; }); // 빈 행이 아닌 경우만 추가 if (Object.values(rowData).some(value => value && value.toString().trim() !== "")) { dataRows.push(rowData); } } }); if (dataRows.length === 0) { throw new Error("Excel 파일에 가져올 데이터가 없습니다."); } // 진행 상황 업데이트를 위한 콜백 const updateProgress = (current: number, total: number) => { const percentage = Math.round((current / total) * 100); setProgress(percentage); }; // 선택된 타입에 따라 적절한 프로세스 함수 호출 let result; if (itemType === "top") { result = await processTopFileImport(dataRows, updateProgress); } else if (itemType === "hull") { result = await processHullFileImport(dataRows, updateProgress); } else { result = await processFileImport(dataRows, updateProgress); } toast.success(`${result.successCount}개의 ${ITEM_TYPE_NAMES[itemType]}이(가) 성공적으로 가져와졌습니다.`); if (result.errorCount > 0) { toast.warning(`${result.errorCount}개의 항목은 처리할 수 없었습니다.`); } // 상태 초기화 및 다이얼로그 닫기 setFile(null); setOpen(false); // 성공 콜백 호출 if (onSuccess) { onSuccess(); } } catch (error) { console.error("Excel 파일 처리 중 오류 발생:", error); setError(error instanceof Error ? error.message : "파일 처리 중 오류가 발생했습니다."); } finally { setIsUploading(false); } }; // 다이얼로그 열기/닫기 핸들러 const handleOpenChange = (newOpen: boolean) => { if (!newOpen) { // 닫을 때 상태 초기화 setFile(null); setError(null); setProgress(0); if (fileInputRef.current) { fileInputRef.current.value = ""; } } setOpen(newOpen); }; return ( <> {ITEM_TYPE_NAMES[itemType]} 가져오기 {ITEM_TYPE_NAMES[itemType]}을 Excel 파일에서 가져옵니다.
올바른 형식의 Excel 파일(.xlsx)을 업로드하세요.
{file && (
선택된 파일: {file.name} ({(file.size / 1024).toFixed(1)} KB)
)} {isUploading && (

{progress}% 완료

)} {error && (
{error}
)}
); }