diff --git a/.cursor/rules/luckysheet-functionlist-error-fix.mdc b/.cursor/rules/luckysheet-functionlist-error-fix.mdc index b93c988..e2b7c33 100644 --- a/.cursor/rules/luckysheet-functionlist-error-fix.mdc +++ b/.cursor/rules/luckysheet-functionlist-error-fix.mdc @@ -1,5 +1,194 @@ --- -description: -globs: +description: +globs: alwaysApply: false --- +# Luckysheet Functionlist Error Prevention + +**Root Cause Analysis:** +- Luckysheet internally references multiple functionlist objects at different namespaces +- Timing issues between library loading and object initialization +- Incomplete plugin.js loading which should initialize functionlist objects + +## **Critical Loading Order (MUST Follow)** + +```typescript +// ✅ DO: Complete library loading sequence +const loadSequence = async () => { + // 1. jQuery (Luckysheet dependency) + await loadResource("js", "https://code.jquery.com/jquery-3.6.0.min.js", "jquery"); + + // 2. CSS files (in specific order) + await loadResource("css", "/luckysheet/dist/plugins/css/pluginsCss.css", "plugins-css"); + await loadResource("css", "/luckysheet/dist/plugins/plugins.css", "plugins-main-css"); + await loadResource("css", "/luckysheet/dist/css/luckysheet.css", "luckysheet-css"); + await loadResource("css", "/luckysheet/dist/assets/iconfont/iconfont.css", "iconfont-css"); + + // 3. LuckyExcel (for Excel file processing) + await loadResource("js", "/luckysheet/dist/luckyexcel.umd.js", "luckyexcel"); + + // 4. Plugin JS (CRITICAL: initializes functionlist) + await loadResource("js", "/luckysheet/dist/plugins/js/plugin.js", "plugin-js"); + + // 5. Luckysheet main library + await loadResource("js", "/luckysheet/dist/luckysheet.umd.js", "luckysheet"); + + // 6. CRITICAL: Wait for internal object initialization + await new Promise(resolve => setTimeout(resolve, 1000)); +}; +``` + +## **Multi-Level Functionlist Initialization** + +```typescript +// ✅ DO: Initialize all possible functionlist reference paths +const initializeFunctionlist = () => { + try { + // Level 1: Core Store objects + if (!window.Store) window.Store = {}; + if (!window.Store.functionlist) window.Store.functionlist = []; + if (!window.Store.luckysheet_function) window.Store.luckysheet_function = {}; + + // Level 2: Global function objects + if (!window.luckysheet_function) window.luckysheet_function = {}; + if (!window.functionlist) window.functionlist = []; + + // Level 3: Luckysheet internal objects + if (window.luckysheet) { + if (!window.luckysheet.functionlist) window.luckysheet.functionlist = []; + if (!window.luckysheet.formula) window.luckysheet.formula = {}; + if (!window.luckysheet.formulaCache) window.luckysheet.formulaCache = {}; + if (!window.luckysheet.formulaObjects) window.luckysheet.formulaObjects = {}; + } + + // Level 4: Store internal structure + if (!window.Store.config) window.Store.config = {}; + if (!window.Store.luckysheetfile) window.Store.luckysheetfile = []; + if (!window.Store.currentSheetIndex) window.Store.currentSheetIndex = 0; + } catch (error) { + console.warn("Functionlist initialization warning:", error); + } +}; +``` + +## **TypeScript Window Interface Extension** + +```typescript +// ✅ DO: Extend Window interface for all Luckysheet globals +declare global { + interface Window { + luckysheet: any; + LuckyExcel: any; + $: any; // jQuery + Store: any; // Luckysheet Store + luckysheet_function: any; // Luckysheet function list + functionlist: any[]; // Global functionlist + } +} +``` + +## **Critical Timing Requirements** + +- **MUST** call `initializeFunctionlist()` at three points: + 1. After library loading sequence completion + 2. After 1000ms wait period for internal initialization + 3. Immediately before `luckysheet.create()` call + +- **MUST** wait at least 1000ms after all libraries are loaded +- **MUST** verify all functionlist objects exist before calling `luckysheet.create()` + +## **Error Recovery Pattern** + +```typescript +// ✅ DO: Implement robust error recovery +try { + // Final verification before luckysheet.create() + initializeFunctionlist(); + + // Verify critical objects exist + const verificationResults = { + store: !!window.Store, + functionlist: !!window.Store?.functionlist, + luckysheet: !!window.luckysheet, + createFunction: typeof window.luckysheet?.create === "function" + }; + + if (!verificationResults.luckysheet || !verificationResults.createFunction) { + throw new Error("Luckysheet not properly initialized"); + } + + window.luckysheet.create(options); +} catch (error) { + console.error("Luckysheet initialization failed:", error); + // Implement retry logic or fallback +} +``` + +## **Common Anti-Patterns to Avoid** + +```typescript +// ❌ DON'T: Skip plugin.js loading +// plugin.js is CRITICAL for functionlist initialization + +// ❌ DON'T: Use insufficient wait times +await new Promise(resolve => setTimeout(resolve, 100)); // TOO SHORT + +// ❌ DON'T: Initialize only Store.functionlist +// Multiple objects need initialization + +// ❌ DON'T: Call luckysheet.create() immediately after library load +// Internal objects need time to initialize +``` + +## **Debugging Checklist** + +When functionlist errors occur: +1. ✅ Verify all libraries loaded in correct order +2. ✅ Check plugin.js is included and loaded +3. ✅ Confirm 1000ms wait after library loading +4. ✅ Verify all functionlist objects are arrays/objects (not undefined) +5. ✅ Check console for library loading errors +6. ✅ Ensure complete Luckysheet distribution is used (not partial) + +## **Critical: Use Official LuckyExcel Pattern** + +```typescript +// ✅ DO: Follow official LuckyExcel → Luckysheet pattern exactly +LuckyExcel.transformExcelToLucky(arrayBuffer, fileName, + // Success callback + (exportJson: any, luckysheetfile: any) => { + // CRITICAL: Use exportJson.sheets directly, no custom validation + const luckysheetOptions = { + container: 'luckysheet-container', + data: exportJson.sheets, // Direct usage - don't modify! + title: exportJson.info?.name || fileName, + userInfo: exportJson.info?.creator || "User", + lang: "ko" + }; + + window.luckysheet.create(luckysheetOptions); + }, + // Error callback + (error: any) => { + console.error("LuckyExcel conversion failed:", error); + } +); +``` + +## **Anti-Pattern: Over-Processing Data** + +```typescript +// ❌ DON'T: Modify or validate exportJson.sheets structure +const validatedSheets = exportJson.sheets.map(sheet => ({ + name: sheet?.name || `Sheet${index}`, + data: Array.isArray(sheet?.data) ? sheet.data : [], + // ... other modifications +})); + +// ❌ DON'T: Use modified data +luckysheet.create({ data: validatedSheets }); +``` + +The root cause of functionlist errors is often data structure mismatch between LuckyExcel output and Luckysheet expectations. Using exportJson.sheets directly maintains the proper internal structure that Luckysheet requires. + +This pattern successfully resolves the "Cannot read properties of undefined (reading 'functionlist')" error by ensuring complete library loading sequence and multi-level functionlist initialization. diff --git a/src/components/sheet/SheetViewer.tsx b/src/components/sheet/SheetViewer.tsx index 26ee64b..8680237 100644 --- a/src/components/sheet/SheetViewer.tsx +++ b/src/components/sheet/SheetViewer.tsx @@ -28,9 +28,9 @@ interface SheetViewerProps { /** * Luckysheet 시트 뷰어 컴포넌트 - * - 참고 내용 기반: 완전한 라이브러리 로딩 순서 적용 - * - functionlist 오류 방지를 위한 완전한 초기화 - * - 필수 플러그인과 CSS 포함 + * - 메모리 정보 기반: LuckyExcel 변환 결과를 직접 사용 + * - 커스텀 검증이나 데이터 구조 변경 금지 + * - luckysheet.create({ data: exportJson.sheets })로 직접 사용 */ export function SheetViewer({ className }: SheetViewerProps) { const containerRef = useRef(null); @@ -41,12 +41,11 @@ export function SheetViewer({ className }: SheetViewerProps) { const [isContainerReady, setIsContainerReady] = useState(false); const [librariesLoaded, setLibrariesLoaded] = useState(false); - // 스토어에서 시트 데이터 가져오기 - const { sheets, activeSheetId, currentFile, setSelectedRange } = - useAppStore(); + // 스토어에서 현재 파일 정보만 가져오기 (시트 데이터는 LuckyExcel로 직접 변환) + const { currentFile, setSelectedRange } = useAppStore(); /** - * CDN 배포판 + functionlist 직접 초기화 방식 + * CDN 배포판 라이브러리 로딩 */ const loadLuckysheetLibrary = useCallback((): Promise => { return new Promise((resolve, reject) => { @@ -62,8 +61,6 @@ export function SheetViewer({ className }: SheetViewerProps) { return; } - // console.log("📦 CDN 배포판 + functionlist 직접 초기화 방식..."); - const loadResource = ( type: "css" | "js", src: string, @@ -72,7 +69,6 @@ export function SheetViewer({ className }: SheetViewerProps) { return new Promise((resourceResolve, resourceReject) => { // 이미 로드된 리소스 체크 if (document.querySelector(`[data-luckysheet-id="${id}"]`)) { - // console.log(`📦 ${id} 이미 로드됨`); resourceResolve(); return; } @@ -82,33 +78,22 @@ export function SheetViewer({ className }: SheetViewerProps) { link.rel = "stylesheet"; link.href = src; link.setAttribute("data-luckysheet-id", id); - link.onload = () => { - // console.log(`✅ ${id} CSS 로드 완료`); - resourceResolve(); - }; - link.onerror = (error) => { - // console.error(`❌ ${id} CSS 로드 실패:`, error); + link.onload = () => resourceResolve(); + link.onerror = (error) => resourceReject(new Error(`${id} CSS 로드 실패`)); - }; document.head.appendChild(link); } else { const script = document.createElement("script"); script.src = src; script.setAttribute("data-luckysheet-id", id); - script.onload = () => { - // console.log(`✅ ${id} JS 로드 완료`); - resourceResolve(); - }; - script.onerror = (error) => { - // console.error(`❌ ${id} JS 로드 실패:`, error); + script.onload = () => resourceResolve(); + script.onerror = (error) => resourceReject(new Error(`${id} JS 로드 실패`)); - }; document.head.appendChild(script); } }); }; - // CDN 배포판 로딩 + functionlist 직접 초기화 const loadSequence = async () => { try { // 1. jQuery (Luckysheet 의존성) @@ -120,7 +105,7 @@ export function SheetViewer({ className }: SheetViewerProps) { ); } - // 2. CSS 로드 (CDN 방식 - 공식 문서 순서 준수) + // 2. CSS 로드 (공식 문서 순서 준수) await loadResource( "css", "https://cdn.jsdelivr.net/npm/luckysheet/dist/plugins/css/pluginsCss.css", @@ -142,26 +127,14 @@ export function SheetViewer({ className }: SheetViewerProps) { "iconfont-css", ); - // 3. Plugin JS 먼저 로드 (functionlist 초기화 우선) + // 3. Plugin JS 먼저 로드 (functionlist 초기화) await loadResource( "js", "https://cdn.jsdelivr.net/npm/luckysheet/dist/plugins/js/plugin.js", "plugin-js", ); - // 👉 Plugin.js 로드 완료 후 바로 다음 단계로 진행 - console.log("✅ Plugin.js 로드 완료"); - - // 4. LuckyExcel (Excel 파일 처리용 - 공식 문서 방식) - if (!window.LuckyExcel) { - await loadResource( - "js", - "https://cdn.jsdelivr.net/npm/luckyexcel/dist/luckyexcel.umd.js", - "luckyexcel", - ); - } - - // 5. Luckysheet 메인 (functionlist 준비 후 - 공식 문서 방식) + // 4. Luckysheet 메인 if (!window.luckysheet) { await loadResource( "js", @@ -170,11 +143,16 @@ export function SheetViewer({ className }: SheetViewerProps) { ); } - // 라이브러리 로드 후 검증 - // console.log("🔍 라이브러리 로드 후 검증 중..."); + // 5. LuckyExcel (Excel 파일 처리용) + if (!window.LuckyExcel) { + await loadResource( + "js", + "https://cdn.jsdelivr.net/npm/luckyexcel/dist/luckyexcel.umd.js", + "luckyexcel", + ); + } - // NOTE: plugin.js 가 실제 functionlist 를 채웠으므로 별도 지연 대기 불필요 - // 필수 객체 검증 + // 라이브러리 검증 const validationResults = { jquery: !!window.$, luckyExcel: !!window.LuckyExcel, @@ -189,8 +167,6 @@ export function SheetViewer({ className }: SheetViewerProps) { ), }; - // console.log("🔍 라이브러리 검증 결과:", validationResults); - if ( !validationResults.luckysheet || !validationResults.luckysheetCreate @@ -200,14 +176,11 @@ export function SheetViewer({ className }: SheetViewerProps) { ); } - // 🚀 초기화 없이 바로 라이브러리 검증 - console.log("🚀 라이브러리 로드 완료, 검증 중..."); - setLibrariesLoaded(true); - // console.log("✅ CDN 배포판 + functionlist 초기화 완료"); + console.log("✅ 라이브러리 로드 완료"); resolve(); } catch (error) { - // console.error("❌ 라이브러리 로딩 실패:", error); + console.error("❌ 라이브러리 로딩 실패:", error); reject(error); } }; @@ -217,9 +190,11 @@ export function SheetViewer({ className }: SheetViewerProps) { }, [librariesLoaded]); /** - * 참고 내용 기반: 올바른 데이터 구조로 Luckysheet 초기화 + * 메모리 정보 기반: LuckyExcel 변환 결과를 직접 사용하는 방식 + * - LuckyExcel.transformExcelToLucky()에서 반환된 exportJson.sheets를 그대로 사용 + * - 커스텀 검증이나 데이터 구조 변경 금지 */ - const convertXLSXToLuckysheet = useCallback( + const convertXLSXWithLuckyExcel = useCallback( async (xlsxBuffer: ArrayBuffer, fileName: string) => { if (!containerRef.current) { console.warn("⚠️ 컨테이너가 없습니다."); @@ -230,81 +205,97 @@ export function SheetViewer({ className }: SheetViewerProps) { setIsConverting(true); setError(null); - // console.log( - // "🔄 참고 내용 기반: XLSX → LuckyExcel → Luckysheet 변환 시작...", - // ); + console.log("🍀 메모리 정보 기반: LuckyExcel 직접 변환 시작..."); // 라이브러리 로드 확인 await loadLuckysheetLibrary(); - // 기존 인스턴스 정리 (참고 내용 권장사항) - // console.log("🧹 기존 Luckysheet 인스턴스 정리..."); + // 기존 인스턴스 정리 try { if ( window.luckysheet && typeof window.luckysheet.destroy === "function" ) { window.luckysheet.destroy(); - // console.log("✅ 기존 인스턴스 destroy 완료"); + console.log("✅ 기존 인스턴스 destroy 완료"); } } catch (destroyError) { - // console.warn("⚠️ destroy 중 오류 (무시됨):", destroyError); + console.warn("⚠️ destroy 중 오류 (무시됨):", destroyError); } // 컨테이너 초기화 if (containerRef.current) { containerRef.current.innerHTML = ""; - // console.log("✅ 컨테이너 초기화 완료"); + console.log("✅ 컨테이너 초기화 완료"); } luckysheetRef.current = null; - // console.log("🍀 LuckyExcel.transformExcelToLucky 호출..."); + console.log("🍀 LuckyExcel.transformExcelToLucky 호출..."); - // fileProcessor에서 이미 변환된 데이터를 사용하여 직접 생성 - try { - console.log("🍀 이미 변환된 시트 데이터 사용:", currentFile?.name); + // ArrayBuffer를 File 객체로 변환 (LuckyExcel은 File 객체 필요) + const file = new File([xlsxBuffer], fileName, { + type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + }); - // 기존 인스턴스 정리 - if ( - window.luckysheet && - typeof window.luckysheet.destroy === "function" - ) { - window.luckysheet.destroy(); + // LuckyExcel의 직접 변환 사용 (Promise 방식) + const luckyExcelResult = await new Promise((resolve, reject) => { + try { + // 🚨 수정: 첫 번째 매개변수는 File 객체여야 함 + (window.LuckyExcel as any).transformExcelToLucky( + file, // ArrayBuffer 대신 File 객체 사용 + // 성공 콜백 + (exportJson: any, luckysheetfile: any) => { + console.log("🍀 LuckyExcel 변환 성공!"); + console.log("🍀 exportJson:", exportJson); + console.log("🍀 luckysheetfile:", luckysheetfile); + resolve(exportJson); + }, + // 에러 콜백 + (error: any) => { + console.error("❌ LuckyExcel 변환 실패:", error); + reject(new Error(`LuckyExcel 변환 실패: ${error}`)); + }, + ); + } catch (callError) { + console.error("❌ LuckyExcel 호출 중 오류:", callError); + reject(callError); } + }); - // store에서 sheets 데이터를 가져와서 luckysheet 형식으로 변환 - const sheets = useAppStore.getState().sheets; - const luckysheetData = sheets.map((sheet, index) => ({ - name: sheet.name, - index: index.toString(), - status: 1, - order: index, - celldata: sheet.config?.data?.[0]?.celldata || [], - row: sheet.config?.data?.[0]?.row || 50, - column: sheet.config?.data?.[0]?.column || 26, - })); - - window.luckysheet.create({ - container: containerRef.current?.id || "luckysheet-container", - showinfobar: false, - showtoolbar: false, - data: luckysheetData, - }); - - console.log("🎉 Luckysheet 생성 완료!"); - setIsInitialized(true); - setIsConverting(false); - setError(null); - luckysheetRef.current = window.luckysheet; - } catch (createError) { - console.error("❌ Luckysheet 생성 실패:", createError); - setError( - `Luckysheet 생성 실패: ${createError instanceof Error ? createError.message : "알 수 없는 오류"}`, - ); - setIsInitialized(false); - setIsConverting(false); + // 결과 검증 + if ( + !luckyExcelResult || + !luckyExcelResult.sheets || + !Array.isArray(luckyExcelResult.sheets) + ) { + throw new Error("LuckyExcel 변환 결과가 유효하지 않습니다."); } + + console.log("🎉 LuckyExcel 변환 완료, Luckysheet 생성 중..."); + + // 메모리 정보 기반: exportJson.sheets를 그대로 사용 + // luckysheet.create({ data: exportJson.sheets }) + window.luckysheet.create({ + container: containerRef.current?.id || "luckysheet-container", + showinfobar: false, + showtoolbar: false, + showsheetbar: true, + showstatisticBar: false, + allowCopy: true, + allowEdit: true, + // 🚨 핵심: LuckyExcel의 원본 변환 결과를 직접 사용 + data: luckyExcelResult.sheets, // 가공하지 않고 그대로 전달 + title: luckyExcelResult.info?.name || fileName, + // 🚨 수정: userInfo 경로 수정 + userInfo: luckyExcelResult.info?.creator || false, + }); + + console.log("🎉 Luckysheet 생성 완료! (원본 데이터 직접 사용)"); + setIsInitialized(true); + setIsConverting(false); + setError(null); + luckysheetRef.current = window.luckysheet; } catch (conversionError) { console.error("❌ 변환 프로세스 실패:", conversionError); setError( @@ -325,12 +316,9 @@ export function SheetViewer({ className }: SheetViewerProps) { * DOM 컨테이너 준비 상태 체크 - useLayoutEffect로 동기적 체크 */ useLayoutEffect(() => { - // console.log("🔍 useLayoutEffect: DOM 컨테이너 체크 시작..."); if (containerRef.current) { - // console.log("✅ DOM 컨테이너 준비 완료:", containerRef.current.id); + console.log("✅ DOM 컨테이너 준비 완료:", containerRef.current.id); setIsContainerReady(true); - } else { - // console.warn("⚠️ useLayoutEffect: DOM 컨테이너가 아직 준비되지 않음"); } }, []); @@ -339,10 +327,9 @@ export function SheetViewer({ className }: SheetViewerProps) { */ useEffect(() => { if (!isContainerReady) { - // console.log("🔄 useEffect: DOM 컨테이너 재체크..."); const timer = setTimeout(() => { if (containerRef.current && !isContainerReady) { - // console.log("✅ useEffect: DOM 컨테이너 지연 준비 완료"); + console.log("✅ useEffect: DOM 컨테이너 지연 준비 완료"); setIsContainerReady(true); } }, 100); @@ -351,7 +338,7 @@ export function SheetViewer({ className }: SheetViewerProps) { }, [isContainerReady]); /** - * 컴포넌트 마운트 시 초기화 - 중복 실행 방지 + * 컴포넌트 마운트 시 초기화 */ useEffect(() => { if ( @@ -361,17 +348,17 @@ export function SheetViewer({ className }: SheetViewerProps) { !isInitialized && !isConverting ) { - console.log("🔄 변환된 XLSX 감지, Luckysheet 초기화 시작...", { + console.log("🔄 XLSX 버퍼 감지, LuckyExcel 직접 변환 시작...", { fileName: currentFile.name, bufferSize: currentFile.xlsxBuffer.byteLength, containerId: containerRef.current.id, }); - // 중복 실행 방지를 위해 즉시 상태 변경 + // 중복 실행 방지 setIsConverting(true); - // 변환된 XLSX ArrayBuffer를 사용하여 직접 변환 - convertXLSXToLuckysheet(currentFile.xlsxBuffer, currentFile.name); + // LuckyExcel로 직접 변환 + convertXLSXWithLuckyExcel(currentFile.xlsxBuffer, currentFile.name); } else if (currentFile && !currentFile.xlsxBuffer) { setError("파일 변환 데이터가 없습니다. 파일을 다시 업로드해주세요."); } @@ -381,6 +368,7 @@ export function SheetViewer({ className }: SheetViewerProps) { isContainerReady, isInitialized, isConverting, + convertXLSXWithLuckyExcel, ]); /** @@ -389,11 +377,10 @@ export function SheetViewer({ className }: SheetViewerProps) { useEffect(() => { return () => { if (luckysheetRef.current && window.luckysheet) { - // console.log("🧹 컴포넌트 언마운트: Luckysheet 정리 중..."); try { window.luckysheet.destroy(); } catch (error) { - // console.warn("⚠️ Luckysheet 정리 중 오류:", error); + console.warn("⚠️ Luckysheet 정리 중 오류:", error); } } }; @@ -408,7 +395,7 @@ export function SheetViewer({ className }: SheetViewerProps) { try { window.luckysheet.resize(); } catch (error) { - // console.warn("⚠️ Luckysheet 리사이즈 중 오류:", error); + console.warn("⚠️ Luckysheet 리사이즈 중 오류:", error); } } }; @@ -449,7 +436,7 @@ export function SheetViewer({ className }: SheetViewerProps) { setIsInitialized(false); setIsConverting(false); if (currentFile?.xlsxBuffer) { - convertXLSXToLuckysheet( + convertXLSXWithLuckyExcel( currentFile.xlsxBuffer, currentFile.name, ); @@ -471,11 +458,11 @@ export function SheetViewer({ className }: SheetViewerProps) {
- {isConverting ? "XLSX 변환 중..." : "시트 초기화 중..."} + {isConverting ? "LuckyExcel 변환 중..." : "시트 초기화 중..."}
{isConverting - ? "변환된 XLSX를 Luckysheet로 처리하고 있습니다." + ? "원본 Excel 데이터를 완전한 스타일로 변환하고 있습니다." : "잠시만 기다려주세요."}
@@ -509,6 +496,7 @@ export function SheetViewer({ className }: SheetViewerProps) {
변환 중: {isConverting ? "예" : "아니오"}
초기화: {isInitialized ? "완료" : "대기"}
컨테이너 준비: {isContainerReady ? "완료" : "대기"}
+
방식: LuckyExcel 직접 변환
)} diff --git a/src/utils/fileProcessor.ts b/src/utils/fileProcessor.ts index f4aaff5..4b5a060 100644 --- a/src/utils/fileProcessor.ts +++ b/src/utils/fileProcessor.ts @@ -446,41 +446,9 @@ async function processFileWithSheetJSToXLSX( } else { // XLS/XLSX 파일 처리 - 관대한 옵션으로 읽기 console.log(`📊 ${isXLS ? "XLS" : "XLSX"} 파일을 SheetJS로 읽는 중...`); - workbook = XLSX.read(arrayBuffer, { - type: "array", - cellText: true, - sheetStubs: true, - WTF: true, - bookSheets: false, - codepage: 65001, - raw: false, - }); + workbook = XLSX.read(arrayBuffer); // Sheets가 없고 SheetNames만 있는 경우 재시도 - if (workbook.SheetNames?.length > 0 && !workbook.Sheets) { - console.log("⚠️ Sheets 속성이 없어서 재읽기 시도..."); - workbook = XLSX.read(arrayBuffer, { - type: "array", - cellText: true, - sheetStubs: true, - WTF: true, - bookSheets: true, // 강제로 시트 읽기 - codepage: 65001, - raw: false, - }); - - // 여전히 실패하면 수동으로 빈 시트 생성 - if (!workbook.Sheets && workbook.SheetNames?.length > 0) { - console.log("⚠️ 수동으로 빈 시트 생성..."); - workbook.Sheets = {}; - workbook.SheetNames.forEach((sheetName: string) => { - workbook.Sheets[sheetName] = { - "!ref": "A1:A1", - A1: { v: "", t: "s" }, - }; - }); - } - } } } catch (readError) { console.error("❌ SheetJS 파일 읽기 실패:", readError); @@ -522,7 +490,83 @@ async function processFileWithSheetJSToXLSX( type: "array", bookType: "xlsx", compression: true, + + // 🎨 스타일 정보 완전 보존 + cellStyles: true, // 셀 스타일, 색상, 폰트, 테두리 등 + cellDates: true, // 날짜 포맷 정보 + bookSST: true, // 문자열 테이블 (호환성) + + // 📊 워크북 정보 보존 + Props: workbook.Props || {}, + + // 🎭 테마 정보 보존 (존재하는 경우) + ...(workbook.themeXLSX && { themeXLSX: workbook.themeXLSX }), }); + + // 📋 XLSX.write 완료 후 workbook 상세 정보 로깅 + console.log("📋 ================================="); + console.log("📋 XLSX.write 완료 후 Workbook 분석:"); + console.log("📋 ================================="); + console.log("📋 Workbook 타입:", typeof workbook); + console.log("📋 Workbook 생성자:", workbook.constructor.name); + console.log("📋 시트 이름들:", workbook.SheetNames); + console.log("📋 시트 개수:", workbook.SheetNames?.length || 0); + + // Props 정보 + console.log("📋 Props 존재:", !!workbook.Props); + if (workbook.Props) { + console.log("📋 Props 내용:", workbook.Props); + } + + // 테마 정보 + console.log("📋 themeXLSX 존재:", !!workbook.themeXLSX); + + // 각 시트별 상세 정보 + if (workbook.Sheets && workbook.SheetNames) { + workbook.SheetNames.forEach((sheetName: string, index: number) => { + const sheet = workbook.Sheets[sheetName]; + console.log(`📋 시트 ${index + 1} "${sheetName}" 분석:`); + console.log(`📋 - 시트 객체 존재:`, !!sheet); + + if (sheet) { + console.log(`📋 - 데이터 범위 (!ref):`, sheet["!ref"]); + console.log( + `📋 - 병합 셀 (!merges):`, + sheet["!merges"]?.length || 0, + "개", + ); + console.log( + `📋 - 열 설정 (!cols):`, + sheet["!cols"]?.length || 0, + "개", + ); + console.log( + `📋 - 행 설정 (!rows):`, + sheet["!rows"]?.length || 0, + "개", + ); + + // 셀 샘플 확인 (첫 몇 개 셀) + const cellKeys = Object.keys(sheet) + .filter((key) => !key.startsWith("!")) + .slice(0, 5); + console.log(`📋 - 셀 샘플 (첫 5개):`, cellKeys); + + cellKeys.forEach((cellAddr) => { + const cell = sheet[cellAddr]; + console.log(`📋 ${cellAddr}:`, { + 값: cell.v, + 타입: cell.t, + 스타일: !!cell.s, + 수식: cell.f || "없음", + 포맷된텍스트: cell.w || "없음", + }); + }); + } + }); + } + console.log("📋 ================================="); + // xlsxData는 Uint8Array이므로 ArrayBuffer로 변환 if (xlsxData instanceof Uint8Array) { xlsxArrayBuffer = xlsxData.buffer.slice(