From 10f90dc68dec42e9a64e081cc0dce6a484447290 Mon Sep 17 00:00:00 2001 From: dujinkim Date: Tue, 29 Jul 2025 11:48:59 +0000 Subject: (대표님, 박서영, 최겸) document-list-only, gtc, vendorDocu, docu-list-rule MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gtc-clauses/table/import-excel-dialog.tsx | 381 +++++++++++++++++++++ 1 file changed, 381 insertions(+) create mode 100644 lib/gtc-contract/gtc-clauses/table/import-excel-dialog.tsx (limited to 'lib/gtc-contract/gtc-clauses/table/import-excel-dialog.tsx') diff --git a/lib/gtc-contract/gtc-clauses/table/import-excel-dialog.tsx b/lib/gtc-contract/gtc-clauses/table/import-excel-dialog.tsx new file mode 100644 index 00000000..f37566fc --- /dev/null +++ b/lib/gtc-contract/gtc-clauses/table/import-excel-dialog.tsx @@ -0,0 +1,381 @@ +"use client" + +import * as React from "react" +import { Upload, Download, FileText, AlertCircle, CheckCircle2, X } from "lucide-react" + +import { Button } from "@/components/ui/button" +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@/components/ui/dialog" +import { + Table, + TableBody, + TableCell, + TableHead, + TableHeader, + TableRow, +} from "@/components/ui/table" +import { Badge } from "@/components/ui/badge" +import { Alert, AlertDescription } from "@/components/ui/alert" +import { ScrollArea } from "@/components/ui/scroll-area" +import { Separator } from "@/components/ui/separator" + +import { type ExcelColumnDef } from "@/lib/export" +import { downloadExcelTemplate, parseExcelFile } from "./excel-import" +import { type GtcClauseTreeView } from "@/db/schema/gtc" +import { toast } from "@/hooks/use-toast" + +interface ImportExcelDialogProps { + documentId: number + columns: ExcelColumnDef[] + onSuccess?: () => void + onImport?: (data: Partial[]) => Promise + trigger?: React.ReactNode +} + +type ImportStep = "upload" | "preview" | "importing" | "complete" + +export function ImportExcelDialog({ + documentId, + columns, + onSuccess, + onImport, + trigger, +}: ImportExcelDialogProps) { + const [open, setOpen] = React.useState(false) + const [step, setStep] = React.useState("upload") + const [selectedFile, setSelectedFile] = React.useState(null) + const [parsedData, setParsedData] = React.useState[]>([]) + const [errors, setErrors] = React.useState([]) + const [isProcessing, setIsProcessing] = React.useState(false) + const fileInputRef = React.useRef(null) + + // 다이얼로그 열기/닫기 시 상태 초기화 + const handleOpenChange = (isOpen: boolean) => { + setOpen(isOpen) + if (!isOpen) { + // 다이얼로그 닫을 때 상태 초기화 + setStep("upload") + setSelectedFile(null) + setParsedData([]) + setErrors([]) + setIsProcessing(false) + if (fileInputRef.current) { + fileInputRef.current.value = "" + } + } + } + + // 템플릿 다운로드 + const handleDownloadTemplate = async () => { + try { + await downloadExcelTemplate(columns, { + filename: `gtc-clauses-template-${new Date().toISOString().split('T')[0]}`, + includeExampleData: true, + useGroupHeader: true, + }) + + toast({ + title: "템플릿 다운로드 완료", + description: "Excel 템플릿이 다운로드되었습니다. 템플릿에 데이터를 입력한 후 업로드해주세요.", + }) + } catch (error) { + toast({ + title: "템플릿 다운로드 실패", + description: "템플릿 다운로드 중 오류가 발생했습니다.", + variant: "destructive", + }) + } + } + + // 파일 선택 + const handleFileSelect = (event: React.ChangeEvent) => { + const file = event.target.files?.[0] + if (file) { + if (file.type !== "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" && + file.type !== "application/vnd.ms-excel") { + toast({ + title: "잘못된 파일 형식", + description: "Excel 파일(.xlsx, .xls)만 업로드할 수 있습니다.", + variant: "destructive", + }) + return + } + setSelectedFile(file) + } + } + + // 파일 파싱 + const handleParseFile = async () => { + if (!selectedFile) return + + setIsProcessing(true) + try { + const result = await parseExcelFile( + selectedFile, + columns, + { + hasGroupHeader: true, + sheetName: "GTC조항템플릿", + } + ) + + setParsedData(result.data) + setErrors(result.errors) + + if (result.errors.length > 0) { + toast({ + title: "파싱 완료 (오류 있음)", + description: `${result.data.length}개의 행을 파싱했지만 ${result.errors.length}개의 오류가 있습니다.`, + variant: "destructive", + }) + } else { + toast({ + title: "파싱 완료", + description: `${result.data.length}개의 행이 성공적으로 파싱되었습니다.`, + }) + } + + setStep("preview") + } catch (error) { + toast({ + title: "파싱 실패", + description: "파일 파싱 중 오류가 발생했습니다.", + variant: "destructive", + }) + } finally { + setIsProcessing(false) + } + } + + // 데이터 가져오기 실행 + const handleImportData = async () => { + if (parsedData.length === 0 || !onImport) return + + setStep("importing") + try { + await onImport(parsedData) + setStep("complete") + + toast({ + title: "가져오기 완료", + description: `${parsedData.length}개의 조항이 성공적으로 가져와졌습니다.`, + }) + + // 성공 콜백 호출 후 잠시 후 다이얼로그 닫기 + setTimeout(() => { + onSuccess?.() + setOpen(false) + }, 2000) + } catch (error) { + toast({ + title: "가져오기 실패", + description: "데이터 가져오기 중 오류가 발생했습니다.", + variant: "destructive", + }) + setStep("preview") + } + } + + const renderUploadStep = () => ( +
+
+ +

Excel 파일로 조항 가져오기

+

+ 먼저 템플릿을 다운로드하여 데이터를 입력한 후 업로드해주세요. +

+
+ +
+
+ +

+ 템플릿에는 입력 가이드와 예시 데이터가 포함되어 있습니다. +

+
+ + + +
+ + +
+ + {selectedFile && ( + + )} +
+
+ ) + + const renderPreviewStep = () => ( +
+
+

데이터 미리보기

+
+ + {parsedData.length}개 행 + + {errors.length > 0 && ( + + {errors.length}개 오류 + + )} +
+
+ + {errors.length > 0 && ( + + + +
+
다음 오류들을 확인해주세요:
+
    + {errors.slice(0, 5).map((error, index) => ( +
  • {error}
  • + ))} + {errors.length > 5 && ( +
  • ... 및 {errors.length - 5}개 추가 오류
  • + )} +
+
+
+
+ )} + + + + + + # + 채번 + 소제목 + 상세항목 + 분류 + 상태 + + + + {parsedData.map((item, index) => ( + + {index + 1} + + {item.itemNumber || "-"} + + + {item.subtitle || "-"} + + + {item.content || "-"} + + {item.category || "-"} + + + {item.isActive ? "활성" : "비활성"} + + + + ))} + +
+
+ +
+ + +
+
+ ) + + const renderImportingStep = () => ( +
+
+

가져오는 중...

+

+ {parsedData.length}개의 조항을 데이터베이스에 저장하고 있습니다. +

+
+ ) + + const renderCompleteStep = () => ( +
+ +

가져오기 완료!

+

+ {parsedData.length}개의 조항이 성공적으로 가져와졌습니다. +

+
+ ) + + return ( + + + {trigger || ( + + )} + + + + Excel에서 조항 가져오기 + + Excel 파일을 사용하여 여러 조항을 한 번에 가져올 수 있습니다. + + + +
+ {step === "upload" && renderUploadStep()} + {step === "preview" && renderPreviewStep()} + {step === "importing" && renderImportingStep()} + {step === "complete" && renderCompleteStep()} +
+
+
+ ) +} \ No newline at end of file -- cgit v1.2.3