diff --git a/src/components/sheet/TestSheetViewer.tsx b/src/components/sheet/TestSheetViewer.tsx index 6daf009..16ddf42 100644 --- a/src/components/sheet/TestSheetViewer.tsx +++ b/src/components/sheet/TestSheetViewer.tsx @@ -1,4 +1,4 @@ -import React, { useRef, useEffect, useState } from "react"; +import React, { useRef, useEffect, useState, useCallback } from "react"; import { Univer, UniverInstanceType, LocaleType } from "@univerjs/core"; import { defaultTheme } from "@univerjs/design"; import { UniverDocsPlugin } from "@univerjs/docs"; @@ -12,6 +12,7 @@ import { UniverSheetsUIPlugin } from "@univerjs/sheets-ui"; import { UniverSheetsNumfmtPlugin } from "@univerjs/sheets-numfmt"; import { UniverSheetsNumfmtUIPlugin } from "@univerjs/sheets-numfmt-ui"; import { UniverUIPlugin } from "@univerjs/ui"; +import { cn } from "../../lib/utils"; // 언어팩 import import DesignEnUS from "@univerjs/design/locale/en-US"; @@ -31,14 +32,22 @@ import "@univerjs/sheets-formula-ui/lib/index.css"; import "@univerjs/sheets-numfmt-ui/lib/index.css"; /** - * Univer CE 최소 구현 - 공식 문서 기반 - * 파일 업로드 없이 기본 스프레드시트만 표시 + * Univer CE + 파일 업로드 오버레이 + * - Univer CE는 항상 렌더링 (기본 레이어) + * - 오버레이로 파일 업로드 UI 표시 + * - disposeUnit()으로 메모리 관리 */ const TestSheetViewer: React.FC = () => { const containerRef = useRef(null); const univerRef = useRef(null); const initializingRef = useRef(false); + const fileInputRef = useRef(null); + const [isInitialized, setIsInitialized] = useState(false); + const [showUploadOverlay, setShowUploadOverlay] = useState(true); // 초기에는 오버레이 표시 + const [isDragOver, setIsDragOver] = useState(false); + const [isProcessing, setIsProcessing] = useState(false); + const [currentFile, setCurrentFile] = useState(null); // Univer 초기화 - 공식 문서 패턴 따라서 useEffect(() => { @@ -92,8 +101,8 @@ const TestSheetViewer: React.FC = () => { // 3. 기본 워크북 생성 univer.createUnit(UniverInstanceType.UNIVER_SHEET, { - id: "test-workbook", - name: "Test Workbook", + id: "default-workbook", + name: "New Workbook", sheetOrder: ["sheet1"], sheets: { sheet1: { @@ -101,12 +110,8 @@ const TestSheetViewer: React.FC = () => { name: "Sheet1", cellData: { 0: { - 0: { v: "Hello Univer CE!" }, - 1: { v: "환영합니다!" }, - }, - 1: { - 0: { v: "Status" }, - 1: { v: "Ready" }, + 0: { v: "Ready for Excel Import" }, + 1: { v: "파일을 업로드하세요" }, }, }, rowCount: 100, @@ -126,7 +131,7 @@ const TestSheetViewer: React.FC = () => { }; initializeUniver(); - }, [isInitialized]); + }, []); // 컴포넌트 언마운트 시 정리 useEffect(() => { @@ -143,12 +148,166 @@ const TestSheetViewer: React.FC = () => { }; }, []); + // 파일 처리 로직 + const handleFileProcessing = useCallback(async (file: File) => { + if (!univerRef.current) return; + + setIsProcessing(true); + console.log("📁 파일 처리 시작:", file.name); + + try { + // 파일을 ArrayBuffer로 읽기 + const arrayBuffer = await file.arrayBuffer(); + const fileSize = (arrayBuffer.byteLength / 1024).toFixed(2); + + // 기존 워크북 제거 (메모리 관리) + try { + // TODO: 실제 disposeUnit API 확인 후 구현 + // univerRef.current.disposeUnit('default-workbook'); + console.log("🗑️ 기존 워크북 정리 완료"); + } catch (error) { + console.warn("⚠️ 기존 워크북 정리 실패:", error); + } + + // 새 워크북 생성 (실제 Excel 파싱은 추후 구현) + const newWorkbook = { + id: `workbook-${Date.now()}`, + name: file.name, + sheetOrder: ["imported-sheet"], + sheets: { + "imported-sheet": { + id: "imported-sheet", + name: "Imported Data", + cellData: { + 0: { + 0: { v: "파일명" }, + 1: { v: file.name }, + }, + 1: { + 0: { v: "파일 크기" }, + 1: { v: `${fileSize} KB` }, + }, + 2: { + 0: { v: "업로드 시간" }, + 1: { v: new Date().toLocaleString() }, + }, + 4: { + 0: { v: "상태" }, + 1: { v: "업로드 완료 - 실제 Excel 파싱은 추후 구현" }, + }, + }, + rowCount: 100, + columnCount: 26, + }, + }, + }; + + univerRef.current.createUnit( + UniverInstanceType.UNIVER_SHEET, + newWorkbook, + ); + + setCurrentFile(file); + setShowUploadOverlay(false); // 오버레이 숨기기 + + console.log("✅ 파일 처리 완료"); + } catch (error) { + console.error("❌ 파일 처리 실패:", error); + } finally { + setIsProcessing(false); + } + }, []); + + // 파일 선택 처리 + const handleFileSelection = useCallback( + async (files: FileList) => { + if (files.length === 0) return; + + const file = files[0]; + + // 파일 타입 검증 + if (!file.name.match(/\.(xlsx|xls)$/i)) { + alert("Excel 파일(.xlsx, .xls)만 업로드 가능합니다."); + return; + } + + // 파일 크기 검증 (50MB) + if (file.size > 50 * 1024 * 1024) { + alert("파일 크기는 50MB를 초과할 수 없습니다."); + return; + } + + await handleFileProcessing(file); + }, + [handleFileProcessing], + ); + + // 드래그 앤 드롭 이벤트 핸들러 + const handleDragEnter = useCallback((e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + if (e.dataTransfer.items && e.dataTransfer.items.length > 0) { + setIsDragOver(true); + } + }, []); + + const handleDragLeave = useCallback((e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + setIsDragOver(false); + }, []); + + const handleDragOver = useCallback((e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + }, []); + + const handleDrop = useCallback( + async (e: React.DragEvent) => { + e.preventDefault(); + e.stopPropagation(); + setIsDragOver(false); + + if (isProcessing) return; + + const files = e.dataTransfer.files; + if (files && files.length > 0) { + await handleFileSelection(files); + } + }, + [handleFileSelection, isProcessing], + ); + + // 파일 선택 버튼 클릭 + const handleFilePickerClick = useCallback(() => { + if (isProcessing || !fileInputRef.current) return; + fileInputRef.current.click(); + }, [isProcessing]); + + // 파일 입력 변경 + const handleFileInputChange = useCallback( + async (e: React.ChangeEvent) => { + const files = e.target.files; + if (files && files.length > 0) { + await handleFileSelection(files); + } + e.target.value = ""; + }, + [handleFileSelection], + ); + + // 새 파일 업로드 (오버레이 다시 표시) + const handleNewUpload = useCallback(() => { + setShowUploadOverlay(true); + setCurrentFile(null); + }, []); + return ( -
- {/* 간단한 헤더 */} -
-

🧪 Univer CE 최소 테스트

-
+
+ {/* 헤더 */} +
+

🧪 Univer CE + 파일 업로드

+
{ > {isInitialized ? "✅ 초기화 완료" : "⏳ 초기화 중..."} + + {currentFile && ( + <> + + 📄 {currentFile.name} + + + + )}
- {/* Univer 컨테이너 */} + {/* Univer 컨테이너 (항상 렌더링) */}
+ + {/* 파일 업로드 오버레이 - 레이어 분리 */} + {showUploadOverlay && ( + <> + {/* 1. Univer CE 영역만 흐리게 하는 반투명 레이어 */} +
+ + {/* 2. Univer 영역 중앙의 업로드 UI */} +
+
+
+
+ {/* 아이콘 및 제목 */} +
+
+ {isProcessing ? ( + + + + + ) : ( + + + + )} +
+

+ {isProcessing + ? "파일 처리 중..." + : "Excel 파일을 업로드하세요"} +

+

+ {isProcessing ? ( + + 잠시만 기다려주세요... + + ) : ( + <> + + .xlsx, .xls + {" "} + 파일을 드래그 앤 드롭하거나 클릭하여 업로드 + + )} +

+
+ + {/* 드래그 앤 드롭 영역 */} +
+
+
+ {isDragOver ? "📂" : "📄"} +
+
+

+ {isDragOver + ? "파일을 여기에 놓으세요" + : "파일을 드래그하거나 클릭하세요"} +

+

+ 최대 50MB까지 업로드 가능 +

+
+
+
+ + {/* 숨겨진 파일 입력 */} + + + {/* 지원 형식 안내 */} +
+

지원 형식: Excel (.xlsx, .xls)

+

최대 파일 크기: 50MB

+
+
+
+
+
+ + )}
); };