"use client"; import React, { useState, useRef } from "react"; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from "@/components/ui/dialog"; import { Button } from "@/components/ui/button"; import { Label } from "@/components/ui/label"; import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue, } from "@/components/ui/select"; import { Input } from "@/components/ui/input"; import { toast } from "sonner"; import { Upload, FileSpreadsheet, AlertCircle, CheckCircle2 } from "lucide-react"; import { importFromExcel, getExcelSheetNames } from "@/lib/utils/excelExport"; import { DynamicFormApi } from "@/lib/api/dynamicForm"; export interface ExcelUploadModalProps { open: boolean; onOpenChange: (open: boolean) => void; tableName: string; uploadMode?: "insert" | "update" | "upsert"; keyColumn?: string; onSuccess?: () => void; } export const ExcelUploadModal: React.FC = ({ open, onOpenChange, tableName, uploadMode = "insert", keyColumn, onSuccess, }) => { const [file, setFile] = useState(null); const [sheetNames, setSheetNames] = useState([]); const [selectedSheet, setSelectedSheet] = useState(""); const [isUploading, setIsUploading] = useState(false); const [previewData, setPreviewData] = useState[]>([]); const fileInputRef = useRef(null); // 파일 선택 핸들러 const handleFileChange = async (e: React.ChangeEvent) => { const selectedFile = e.target.files?.[0]; if (!selectedFile) return; // 파일 확장자 검증 const fileExtension = selectedFile.name.split(".").pop()?.toLowerCase(); if (!["xlsx", "xls", "csv"].includes(fileExtension || "")) { toast.error("엑셀 파일만 업로드 가능합니다. (.xlsx, .xls, .csv)"); return; } setFile(selectedFile); try { // 시트 목록 가져오기 const sheets = await getExcelSheetNames(selectedFile); setSheetNames(sheets); setSelectedSheet(sheets[0] || ""); // 미리보기 데이터 로드 (첫 5행) const data = await importFromExcel(selectedFile, sheets[0]); setPreviewData(data.slice(0, 5)); toast.success(`파일이 선택되었습니다: ${selectedFile.name}`); } catch (error) { console.error("파일 읽기 오류:", error); toast.error("파일을 읽는 중 오류가 발생했습니다."); setFile(null); } }; // 시트 변경 핸들러 const handleSheetChange = async (sheetName: string) => { setSelectedSheet(sheetName); if (!file) return; try { const data = await importFromExcel(file, sheetName); setPreviewData(data.slice(0, 5)); } catch (error) { console.error("시트 읽기 오류:", error); toast.error("시트를 읽는 중 오류가 발생했습니다."); } }; // 업로드 핸들러 const handleUpload = async () => { if (!file) { toast.error("파일을 선택해주세요."); return; } if (!tableName) { toast.error("테이블명이 지정되지 않았습니다."); return; } setIsUploading(true); try { // 엑셀 데이터 읽기 const data = await importFromExcel(file, selectedSheet); console.log("📤 엑셀 업로드 시작:", { tableName, uploadMode, rowCount: data.length, }); // 업로드 모드에 따라 처리 let successCount = 0; let failCount = 0; for (const row of data) { try { if (uploadMode === "insert") { // 삽입 모드 const formData = { screenId: 0, tableName, data: row }; const result = await DynamicFormApi.saveFormData(formData); if (result.success) { successCount++; } else { console.error("저장 실패:", result.message, row); failCount++; } } else if (uploadMode === "update" && keyColumn) { // 업데이트 모드 const keyValue = row[keyColumn]; if (keyValue) { await DynamicFormApi.updateFormDataPartial(tableName, keyValue, row); successCount++; } else { failCount++; } } else if (uploadMode === "upsert" && keyColumn) { // Upsert 모드 (있으면 업데이트, 없으면 삽입) const keyValue = row[keyColumn]; if (keyValue) { try { const updateResult = await DynamicFormApi.updateFormDataPartial(tableName, keyValue, row); if (!updateResult.success) { // 업데이트 실패 시 삽입 시도 const formData = { screenId: 0, tableName, data: row }; const insertResult = await DynamicFormApi.saveFormData(formData); if (insertResult.success) { successCount++; } else { console.error("Upsert 실패:", insertResult.message, row); failCount++; } } else { successCount++; } } catch { const formData = { screenId: 0, tableName, data: row }; const insertResult = await DynamicFormApi.saveFormData(formData); if (insertResult.success) { successCount++; } else { console.error("Upsert 실패:", insertResult.message, row); failCount++; } } } else { const formData = { screenId: 0, tableName, data: row }; const result = await DynamicFormApi.saveFormData(formData); if (result.success) { successCount++; } else { console.error("저장 실패:", result.message, row); failCount++; } } } } catch (error) { console.error("행 처리 오류:", row, error); failCount++; } } console.log("✅ 엑셀 업로드 완료:", { successCount, failCount, totalCount: data.length, }); if (successCount > 0) { toast.success(`${successCount}개 행이 업로드되었습니다.${failCount > 0 ? ` (실패: ${failCount}개)` : ""}`); // onSuccess 내부에서 closeModal이 호출되므로 여기서는 호출하지 않음 onSuccess?.(); // onOpenChange(false); // 제거: onSuccess에서 이미 모달을 닫음 } else { toast.error("업로드에 실패했습니다."); } } catch (error) { console.error("❌ 엑셀 업로드 실패:", error); toast.error("엑셀 업로드 중 오류가 발생했습니다."); } finally { setIsUploading(false); } }; return ( 엑셀 파일 업로드 엑셀 파일을 선택하여 데이터를 업로드하세요.
{/* 파일 선택 */}

지원 형식: .xlsx, .xls, .csv

{/* 시트 선택 */} {sheetNames.length > 0 && (
)} {/* 업로드 모드 정보 */}

업로드 모드: {uploadMode === "insert" ? "삽입" : uploadMode === "update" ? "업데이트" : "Upsert"}

{uploadMode === "insert" && "새로운 데이터로 삽입됩니다."} {uploadMode === "update" && `기존 데이터를 업데이트합니다. (키: ${keyColumn})`} {uploadMode === "upsert" && `있으면 업데이트, 없으면 삽입합니다. (키: ${keyColumn})`}

{/* 미리보기 */} {previewData.length > 0 && (
{Object.keys(previewData[0]).map((key) => ( ))} {previewData.map((row, index) => ( {Object.values(row).map((value, i) => ( ))} ))}
{key}
{String(value)}
총 {previewData.length}개 행 (미리보기)
)}
); };