"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 { importTechVendorsFromExcel } from "../service" import { decryptWithServerAction } from "@/components/drm/drmUtils" interface ImportTechVendorButtonProps { onSuccess?: () => void; } export function ImportTechVendorButton({ onSuccess }: ImportTechVendorButtonProps) { 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 === "vendorName")) { 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 = ["업체명", "이메일", "사업자등록번호", "벤더타입"]; const alternativeHeaders = { "업체명": ["vendorName"], "업체코드": ["vendorCode"], "이메일": ["email"], "사업자등록번호": ["taxId"], "국가": ["country"], "영문국가명": ["countryEng"], "제조국": ["countryFab"], "에이전트명": ["agentName"], "에이전트연락처": ["agentPhone"], "에이전트이메일": ["agentEmail"], "주소": ["address"], "전화번호": ["phone"], "웹사이트": ["website"], "벤더타입": ["techVendorType"], "대표자명": ["representativeName"], "대표자이메일": ["representativeEmail"], "대표자연락처": ["representativePhone"], "대표자생년월일": ["representativeBirth"], "담당자명": ["contactName"], "담당자직책": ["contactPosition"], "담당자이메일": ["contactEmail"], "담당자연락처": ["contactPhone"], "담당자국가": ["contactCountry"], "아이템": ["items"] }; // 헤더 매핑 확인 (대체 이름 포함) 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 파일에 가져올 데이터가 없습니다."); } setProgress(70); // 벤더 데이터 처리 const vendors = dataRows.map(row => { const vendorEmail = row["이메일"] || row["email"] || ""; const contactName = row["담당자명"] || row["contactName"] || ""; const contactEmail = row["담당자이메일"] || row["contactEmail"] || ""; // 담당자 정보 처리: 담당자가 없으면 벤더 이메일을 기본 담당자로 사용 const contacts = []; if (contactName && contactEmail) { // 명시적인 담당자가 있는 경우 contacts.push({ contactName: contactName, contactPosition: row["담당자직책"] || row["contactPosition"] || "", contactEmail: contactEmail, contactPhone: row["담당자연락처"] || row["contactPhone"] || "", country: row["담당자국가"] || row["contactCountry"] || null, isPrimary: true }); } else if (vendorEmail) { // 담당자 정보가 없으면 벤더 정보를 기본 담당자로 사용 const representativeName = row["대표자명"] || row["representativeName"]; contacts.push({ contactName: representativeName || row["업체명"] || row["vendorName"] || "기본 담당자", contactPosition: "기본 담당자", contactEmail: vendorEmail, contactPhone: row["대표자연락처"] || row["representativePhone"] || row["전화번호"] || row["phone"] || "", country: row["국가"] || row["country"] || null, isPrimary: true }); } return { vendorName: row["업체명"] || row["vendorName"] || "", vendorCode: row["업체코드"] || row["vendorCode"] || null, email: vendorEmail, taxId: row["사업자등록번호"] || row["taxId"] || "", country: row["국가"] || row["country"] || null, countryEng: row["영문국가명"] || row["countryEng"] || null, countryFab: row["제조국"] || row["countryFab"] || null, agentName: row["에이전트명"] || row["agentName"] || null, agentPhone: row["에이전트연락처"] || row["agentPhone"] || null, agentEmail: row["에이전트이메일"] || row["agentEmail"] || null, address: row["주소"] || row["address"] || null, phone: row["전화번호"] || row["phone"] || null, website: row["웹사이트"] || row["website"] || null, techVendorType: row["벤더타입"] || row["techVendorType"] || "", representativeName: row["대표자명"] || row["representativeName"] || null, representativeEmail: row["대표자이메일"] || row["representativeEmail"] || null, representativePhone: row["대표자연락처"] || row["representativePhone"] || null, representativeBirth: row["대표자생년월일"] || row["representativeBirth"] || null, items: row["아이템"] || row["items"] || "", contacts: contacts }; }); setProgress(90); toast.info(`${vendors.length}개 벤더 데이터를 서버로 전송 중...`); // 벤더 데이터 가져오기 실행 const result = await importTechVendorsFromExcel(vendors); setProgress(100); if (result.success) { // 상세한 결과 메시지 표시 if (result.message) { toast.success(`가져오기 완료: ${result.message}`); } else { toast.success(`${vendors.length}개의 기술영업 벤더가 성공적으로 가져와졌습니다.`); } // 스킵된 벤더가 있으면 경고 메시지 추가 if (result.details?.skipped && result.details.skipped.length > 0) { setTimeout(() => { const skippedList = result.details.skipped .map(item => `${item.vendorName} (${item.email}): ${item.reason}`) .slice(0, 3) // 최대 3개만 표시 .join('\n'); const moreText = result.details.skipped.length > 3 ? `\n... 외 ${result.details.skipped.length - 3}개` : ''; toast.warning(`중복으로 스킵된 벤더:\n${skippedList}${moreText}`); }, 1000); } // 오류가 있으면 오류 메시지 추가 if (result.details?.errors && result.details.errors.length > 0) { setTimeout(() => { const errorList = result.details.errors .map(item => `${item.vendorName} (${item.email}): ${item.error}`) .slice(0, 3) // 최대 3개만 표시 .join('\n'); const moreText = result.details.errors.length > 3 ? `\n... 외 ${result.details.errors.length - 3}개` : ''; toast.error(`처리 중 오류 발생:\n${errorList}${moreText}`); }, 2000); } } else { toast.error(result.error || "벤더 가져오기에 실패했습니다."); } // 상태 초기화 및 다이얼로그 닫기 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 ( <> 기술영업 벤더 가져오기 기술영업 벤더를 Excel 파일에서 가져옵니다.
올바른 형식의 Excel 파일(.xlsx)을 업로드하세요.
{file && (
선택된 파일: {file.name} ({(file.size / 1024).toFixed(1)} KB)
)} {isUploading && (

{progress}% 완료

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