Fix Luckysheet functionlist error and improve file processing
This commit is contained in:
@@ -1,5 +1,194 @@
|
|||||||
---
|
---
|
||||||
description:
|
description:
|
||||||
globs:
|
globs:
|
||||||
alwaysApply: false
|
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.
|
||||||
|
|||||||
@@ -28,9 +28,9 @@ interface SheetViewerProps {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Luckysheet 시트 뷰어 컴포넌트
|
* Luckysheet 시트 뷰어 컴포넌트
|
||||||
* - 참고 내용 기반: 완전한 라이브러리 로딩 순서 적용
|
* - 메모리 정보 기반: LuckyExcel 변환 결과를 직접 사용
|
||||||
* - functionlist 오류 방지를 위한 완전한 초기화
|
* - 커스텀 검증이나 데이터 구조 변경 금지
|
||||||
* - 필수 플러그인과 CSS 포함
|
* - luckysheet.create({ data: exportJson.sheets })로 직접 사용
|
||||||
*/
|
*/
|
||||||
export function SheetViewer({ className }: SheetViewerProps) {
|
export function SheetViewer({ className }: SheetViewerProps) {
|
||||||
const containerRef = useRef<HTMLDivElement>(null);
|
const containerRef = useRef<HTMLDivElement>(null);
|
||||||
@@ -41,12 +41,11 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
|||||||
const [isContainerReady, setIsContainerReady] = useState(false);
|
const [isContainerReady, setIsContainerReady] = useState(false);
|
||||||
const [librariesLoaded, setLibrariesLoaded] = useState(false);
|
const [librariesLoaded, setLibrariesLoaded] = useState(false);
|
||||||
|
|
||||||
// 스토어에서 시트 데이터 가져오기
|
// 스토어에서 현재 파일 정보만 가져오기 (시트 데이터는 LuckyExcel로 직접 변환)
|
||||||
const { sheets, activeSheetId, currentFile, setSelectedRange } =
|
const { currentFile, setSelectedRange } = useAppStore();
|
||||||
useAppStore();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* CDN 배포판 + functionlist 직접 초기화 방식
|
* CDN 배포판 라이브러리 로딩
|
||||||
*/
|
*/
|
||||||
const loadLuckysheetLibrary = useCallback((): Promise<void> => {
|
const loadLuckysheetLibrary = useCallback((): Promise<void> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
@@ -62,8 +61,6 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// console.log("📦 CDN 배포판 + functionlist 직접 초기화 방식...");
|
|
||||||
|
|
||||||
const loadResource = (
|
const loadResource = (
|
||||||
type: "css" | "js",
|
type: "css" | "js",
|
||||||
src: string,
|
src: string,
|
||||||
@@ -72,7 +69,6 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
|||||||
return new Promise((resourceResolve, resourceReject) => {
|
return new Promise((resourceResolve, resourceReject) => {
|
||||||
// 이미 로드된 리소스 체크
|
// 이미 로드된 리소스 체크
|
||||||
if (document.querySelector(`[data-luckysheet-id="${id}"]`)) {
|
if (document.querySelector(`[data-luckysheet-id="${id}"]`)) {
|
||||||
// console.log(`📦 ${id} 이미 로드됨`);
|
|
||||||
resourceResolve();
|
resourceResolve();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -82,33 +78,22 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
|||||||
link.rel = "stylesheet";
|
link.rel = "stylesheet";
|
||||||
link.href = src;
|
link.href = src;
|
||||||
link.setAttribute("data-luckysheet-id", id);
|
link.setAttribute("data-luckysheet-id", id);
|
||||||
link.onload = () => {
|
link.onload = () => resourceResolve();
|
||||||
// console.log(`✅ ${id} CSS 로드 완료`);
|
link.onerror = (error) =>
|
||||||
resourceResolve();
|
|
||||||
};
|
|
||||||
link.onerror = (error) => {
|
|
||||||
// console.error(`❌ ${id} CSS 로드 실패:`, error);
|
|
||||||
resourceReject(new Error(`${id} CSS 로드 실패`));
|
resourceReject(new Error(`${id} CSS 로드 실패`));
|
||||||
};
|
|
||||||
document.head.appendChild(link);
|
document.head.appendChild(link);
|
||||||
} else {
|
} else {
|
||||||
const script = document.createElement("script");
|
const script = document.createElement("script");
|
||||||
script.src = src;
|
script.src = src;
|
||||||
script.setAttribute("data-luckysheet-id", id);
|
script.setAttribute("data-luckysheet-id", id);
|
||||||
script.onload = () => {
|
script.onload = () => resourceResolve();
|
||||||
// console.log(`✅ ${id} JS 로드 완료`);
|
script.onerror = (error) =>
|
||||||
resourceResolve();
|
|
||||||
};
|
|
||||||
script.onerror = (error) => {
|
|
||||||
// console.error(`❌ ${id} JS 로드 실패:`, error);
|
|
||||||
resourceReject(new Error(`${id} JS 로드 실패`));
|
resourceReject(new Error(`${id} JS 로드 실패`));
|
||||||
};
|
|
||||||
document.head.appendChild(script);
|
document.head.appendChild(script);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// CDN 배포판 로딩 + functionlist 직접 초기화
|
|
||||||
const loadSequence = async () => {
|
const loadSequence = async () => {
|
||||||
try {
|
try {
|
||||||
// 1. jQuery (Luckysheet 의존성)
|
// 1. jQuery (Luckysheet 의존성)
|
||||||
@@ -120,7 +105,7 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. CSS 로드 (CDN 방식 - 공식 문서 순서 준수)
|
// 2. CSS 로드 (공식 문서 순서 준수)
|
||||||
await loadResource(
|
await loadResource(
|
||||||
"css",
|
"css",
|
||||||
"https://cdn.jsdelivr.net/npm/luckysheet/dist/plugins/css/pluginsCss.css",
|
"https://cdn.jsdelivr.net/npm/luckysheet/dist/plugins/css/pluginsCss.css",
|
||||||
@@ -142,26 +127,14 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
|||||||
"iconfont-css",
|
"iconfont-css",
|
||||||
);
|
);
|
||||||
|
|
||||||
// 3. Plugin JS 먼저 로드 (functionlist 초기화 우선)
|
// 3. Plugin JS 먼저 로드 (functionlist 초기화)
|
||||||
await loadResource(
|
await loadResource(
|
||||||
"js",
|
"js",
|
||||||
"https://cdn.jsdelivr.net/npm/luckysheet/dist/plugins/js/plugin.js",
|
"https://cdn.jsdelivr.net/npm/luckysheet/dist/plugins/js/plugin.js",
|
||||||
"plugin-js",
|
"plugin-js",
|
||||||
);
|
);
|
||||||
|
|
||||||
// 👉 Plugin.js 로드 완료 후 바로 다음 단계로 진행
|
// 4. Luckysheet 메인
|
||||||
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 준비 후 - 공식 문서 방식)
|
|
||||||
if (!window.luckysheet) {
|
if (!window.luckysheet) {
|
||||||
await loadResource(
|
await loadResource(
|
||||||
"js",
|
"js",
|
||||||
@@ -170,11 +143,16 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 라이브러리 로드 후 검증
|
// 5. LuckyExcel (Excel 파일 처리용)
|
||||||
// console.log("🔍 라이브러리 로드 후 검증 중...");
|
if (!window.LuckyExcel) {
|
||||||
|
await loadResource(
|
||||||
|
"js",
|
||||||
|
"https://cdn.jsdelivr.net/npm/luckyexcel/dist/luckyexcel.umd.js",
|
||||||
|
"luckyexcel",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE: plugin.js 가 실제 functionlist 를 채웠으므로 별도 지연 대기 불필요
|
// 라이브러리 검증
|
||||||
// 필수 객체 검증
|
|
||||||
const validationResults = {
|
const validationResults = {
|
||||||
jquery: !!window.$,
|
jquery: !!window.$,
|
||||||
luckyExcel: !!window.LuckyExcel,
|
luckyExcel: !!window.LuckyExcel,
|
||||||
@@ -189,8 +167,6 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
|||||||
),
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
// console.log("🔍 라이브러리 검증 결과:", validationResults);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!validationResults.luckysheet ||
|
!validationResults.luckysheet ||
|
||||||
!validationResults.luckysheetCreate
|
!validationResults.luckysheetCreate
|
||||||
@@ -200,14 +176,11 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 🚀 초기화 없이 바로 라이브러리 검증
|
|
||||||
console.log("🚀 라이브러리 로드 완료, 검증 중...");
|
|
||||||
|
|
||||||
setLibrariesLoaded(true);
|
setLibrariesLoaded(true);
|
||||||
// console.log("✅ CDN 배포판 + functionlist 초기화 완료");
|
console.log("✅ 라이브러리 로드 완료");
|
||||||
resolve();
|
resolve();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.error("❌ 라이브러리 로딩 실패:", error);
|
console.error("❌ 라이브러리 로딩 실패:", error);
|
||||||
reject(error);
|
reject(error);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -217,9 +190,11 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
|||||||
}, [librariesLoaded]);
|
}, [librariesLoaded]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 참고 내용 기반: 올바른 데이터 구조로 Luckysheet 초기화
|
* 메모리 정보 기반: LuckyExcel 변환 결과를 직접 사용하는 방식
|
||||||
|
* - LuckyExcel.transformExcelToLucky()에서 반환된 exportJson.sheets를 그대로 사용
|
||||||
|
* - 커스텀 검증이나 데이터 구조 변경 금지
|
||||||
*/
|
*/
|
||||||
const convertXLSXToLuckysheet = useCallback(
|
const convertXLSXWithLuckyExcel = useCallback(
|
||||||
async (xlsxBuffer: ArrayBuffer, fileName: string) => {
|
async (xlsxBuffer: ArrayBuffer, fileName: string) => {
|
||||||
if (!containerRef.current) {
|
if (!containerRef.current) {
|
||||||
console.warn("⚠️ 컨테이너가 없습니다.");
|
console.warn("⚠️ 컨테이너가 없습니다.");
|
||||||
@@ -230,81 +205,97 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
|||||||
setIsConverting(true);
|
setIsConverting(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
// console.log(
|
console.log("🍀 메모리 정보 기반: LuckyExcel 직접 변환 시작...");
|
||||||
// "🔄 참고 내용 기반: XLSX → LuckyExcel → Luckysheet 변환 시작...",
|
|
||||||
// );
|
|
||||||
|
|
||||||
// 라이브러리 로드 확인
|
// 라이브러리 로드 확인
|
||||||
await loadLuckysheetLibrary();
|
await loadLuckysheetLibrary();
|
||||||
|
|
||||||
// 기존 인스턴스 정리 (참고 내용 권장사항)
|
// 기존 인스턴스 정리
|
||||||
// console.log("🧹 기존 Luckysheet 인스턴스 정리...");
|
|
||||||
try {
|
try {
|
||||||
if (
|
if (
|
||||||
window.luckysheet &&
|
window.luckysheet &&
|
||||||
typeof window.luckysheet.destroy === "function"
|
typeof window.luckysheet.destroy === "function"
|
||||||
) {
|
) {
|
||||||
window.luckysheet.destroy();
|
window.luckysheet.destroy();
|
||||||
// console.log("✅ 기존 인스턴스 destroy 완료");
|
console.log("✅ 기존 인스턴스 destroy 완료");
|
||||||
}
|
}
|
||||||
} catch (destroyError) {
|
} catch (destroyError) {
|
||||||
// console.warn("⚠️ destroy 중 오류 (무시됨):", destroyError);
|
console.warn("⚠️ destroy 중 오류 (무시됨):", destroyError);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 컨테이너 초기화
|
// 컨테이너 초기화
|
||||||
if (containerRef.current) {
|
if (containerRef.current) {
|
||||||
containerRef.current.innerHTML = "";
|
containerRef.current.innerHTML = "";
|
||||||
// console.log("✅ 컨테이너 초기화 완료");
|
console.log("✅ 컨테이너 초기화 완료");
|
||||||
}
|
}
|
||||||
|
|
||||||
luckysheetRef.current = null;
|
luckysheetRef.current = null;
|
||||||
|
|
||||||
// console.log("🍀 LuckyExcel.transformExcelToLucky 호출...");
|
console.log("🍀 LuckyExcel.transformExcelToLucky 호출...");
|
||||||
|
|
||||||
// fileProcessor에서 이미 변환된 데이터를 사용하여 직접 생성
|
// ArrayBuffer를 File 객체로 변환 (LuckyExcel은 File 객체 필요)
|
||||||
try {
|
const file = new File([xlsxBuffer], fileName, {
|
||||||
console.log("🍀 이미 변환된 시트 데이터 사용:", currentFile?.name);
|
type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
||||||
|
});
|
||||||
|
|
||||||
// 기존 인스턴스 정리
|
// LuckyExcel의 직접 변환 사용 (Promise 방식)
|
||||||
if (
|
const luckyExcelResult = await new Promise<any>((resolve, reject) => {
|
||||||
window.luckysheet &&
|
try {
|
||||||
typeof window.luckysheet.destroy === "function"
|
// 🚨 수정: 첫 번째 매개변수는 File 객체여야 함
|
||||||
) {
|
(window.LuckyExcel as any).transformExcelToLucky(
|
||||||
window.luckysheet.destroy();
|
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;
|
if (
|
||||||
const luckysheetData = sheets.map((sheet, index) => ({
|
!luckyExcelResult ||
|
||||||
name: sheet.name,
|
!luckyExcelResult.sheets ||
|
||||||
index: index.toString(),
|
!Array.isArray(luckyExcelResult.sheets)
|
||||||
status: 1,
|
) {
|
||||||
order: index,
|
throw new Error("LuckyExcel 변환 결과가 유효하지 않습니다.");
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) {
|
} catch (conversionError) {
|
||||||
console.error("❌ 변환 프로세스 실패:", conversionError);
|
console.error("❌ 변환 프로세스 실패:", conversionError);
|
||||||
setError(
|
setError(
|
||||||
@@ -325,12 +316,9 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
|||||||
* DOM 컨테이너 준비 상태 체크 - useLayoutEffect로 동기적 체크
|
* DOM 컨테이너 준비 상태 체크 - useLayoutEffect로 동기적 체크
|
||||||
*/
|
*/
|
||||||
useLayoutEffect(() => {
|
useLayoutEffect(() => {
|
||||||
// console.log("🔍 useLayoutEffect: DOM 컨테이너 체크 시작...");
|
|
||||||
if (containerRef.current) {
|
if (containerRef.current) {
|
||||||
// console.log("✅ DOM 컨테이너 준비 완료:", containerRef.current.id);
|
console.log("✅ DOM 컨테이너 준비 완료:", containerRef.current.id);
|
||||||
setIsContainerReady(true);
|
setIsContainerReady(true);
|
||||||
} else {
|
|
||||||
// console.warn("⚠️ useLayoutEffect: DOM 컨테이너가 아직 준비되지 않음");
|
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
@@ -339,10 +327,9 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
|||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!isContainerReady) {
|
if (!isContainerReady) {
|
||||||
// console.log("🔄 useEffect: DOM 컨테이너 재체크...");
|
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
if (containerRef.current && !isContainerReady) {
|
if (containerRef.current && !isContainerReady) {
|
||||||
// console.log("✅ useEffect: DOM 컨테이너 지연 준비 완료");
|
console.log("✅ useEffect: DOM 컨테이너 지연 준비 완료");
|
||||||
setIsContainerReady(true);
|
setIsContainerReady(true);
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
@@ -351,7 +338,7 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
|||||||
}, [isContainerReady]);
|
}, [isContainerReady]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 컴포넌트 마운트 시 초기화 - 중복 실행 방지
|
* 컴포넌트 마운트 시 초기화
|
||||||
*/
|
*/
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (
|
if (
|
||||||
@@ -361,17 +348,17 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
|||||||
!isInitialized &&
|
!isInitialized &&
|
||||||
!isConverting
|
!isConverting
|
||||||
) {
|
) {
|
||||||
console.log("🔄 변환된 XLSX 감지, Luckysheet 초기화 시작...", {
|
console.log("🔄 XLSX 버퍼 감지, LuckyExcel 직접 변환 시작...", {
|
||||||
fileName: currentFile.name,
|
fileName: currentFile.name,
|
||||||
bufferSize: currentFile.xlsxBuffer.byteLength,
|
bufferSize: currentFile.xlsxBuffer.byteLength,
|
||||||
containerId: containerRef.current.id,
|
containerId: containerRef.current.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 중복 실행 방지를 위해 즉시 상태 변경
|
// 중복 실행 방지
|
||||||
setIsConverting(true);
|
setIsConverting(true);
|
||||||
|
|
||||||
// 변환된 XLSX ArrayBuffer를 사용하여 직접 변환
|
// LuckyExcel로 직접 변환
|
||||||
convertXLSXToLuckysheet(currentFile.xlsxBuffer, currentFile.name);
|
convertXLSXWithLuckyExcel(currentFile.xlsxBuffer, currentFile.name);
|
||||||
} else if (currentFile && !currentFile.xlsxBuffer) {
|
} else if (currentFile && !currentFile.xlsxBuffer) {
|
||||||
setError("파일 변환 데이터가 없습니다. 파일을 다시 업로드해주세요.");
|
setError("파일 변환 데이터가 없습니다. 파일을 다시 업로드해주세요.");
|
||||||
}
|
}
|
||||||
@@ -381,6 +368,7 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
|||||||
isContainerReady,
|
isContainerReady,
|
||||||
isInitialized,
|
isInitialized,
|
||||||
isConverting,
|
isConverting,
|
||||||
|
convertXLSXWithLuckyExcel,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -389,11 +377,10 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
if (luckysheetRef.current && window.luckysheet) {
|
if (luckysheetRef.current && window.luckysheet) {
|
||||||
// console.log("🧹 컴포넌트 언마운트: Luckysheet 정리 중...");
|
|
||||||
try {
|
try {
|
||||||
window.luckysheet.destroy();
|
window.luckysheet.destroy();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.warn("⚠️ Luckysheet 정리 중 오류:", error);
|
console.warn("⚠️ Luckysheet 정리 중 오류:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -408,7 +395,7 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
|||||||
try {
|
try {
|
||||||
window.luckysheet.resize();
|
window.luckysheet.resize();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.warn("⚠️ Luckysheet 리사이즈 중 오류:", error);
|
console.warn("⚠️ Luckysheet 리사이즈 중 오류:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -449,7 +436,7 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
|||||||
setIsInitialized(false);
|
setIsInitialized(false);
|
||||||
setIsConverting(false);
|
setIsConverting(false);
|
||||||
if (currentFile?.xlsxBuffer) {
|
if (currentFile?.xlsxBuffer) {
|
||||||
convertXLSXToLuckysheet(
|
convertXLSXWithLuckyExcel(
|
||||||
currentFile.xlsxBuffer,
|
currentFile.xlsxBuffer,
|
||||||
currentFile.name,
|
currentFile.name,
|
||||||
);
|
);
|
||||||
@@ -471,11 +458,11 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
|||||||
<div className="text-center p-6">
|
<div className="text-center p-6">
|
||||||
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
|
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mx-auto mb-4"></div>
|
||||||
<div className="text-blue-600 text-lg font-semibold mb-2">
|
<div className="text-blue-600 text-lg font-semibold mb-2">
|
||||||
{isConverting ? "XLSX 변환 중..." : "시트 초기화 중..."}
|
{isConverting ? "LuckyExcel 변환 중..." : "시트 초기화 중..."}
|
||||||
</div>
|
</div>
|
||||||
<div className="text-blue-500 text-sm">
|
<div className="text-blue-500 text-sm">
|
||||||
{isConverting
|
{isConverting
|
||||||
? "변환된 XLSX를 Luckysheet로 처리하고 있습니다."
|
? "원본 Excel 데이터를 완전한 스타일로 변환하고 있습니다."
|
||||||
: "잠시만 기다려주세요."}
|
: "잠시만 기다려주세요."}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -509,6 +496,7 @@ export function SheetViewer({ className }: SheetViewerProps) {
|
|||||||
<div>변환 중: {isConverting ? "예" : "아니오"}</div>
|
<div>변환 중: {isConverting ? "예" : "아니오"}</div>
|
||||||
<div>초기화: {isInitialized ? "완료" : "대기"}</div>
|
<div>초기화: {isInitialized ? "완료" : "대기"}</div>
|
||||||
<div>컨테이너 준비: {isContainerReady ? "완료" : "대기"}</div>
|
<div>컨테이너 준비: {isContainerReady ? "완료" : "대기"}</div>
|
||||||
|
<div>방식: LuckyExcel 직접 변환</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -446,41 +446,9 @@ async function processFileWithSheetJSToXLSX(
|
|||||||
} else {
|
} else {
|
||||||
// XLS/XLSX 파일 처리 - 관대한 옵션으로 읽기
|
// XLS/XLSX 파일 처리 - 관대한 옵션으로 읽기
|
||||||
console.log(`📊 ${isXLS ? "XLS" : "XLSX"} 파일을 SheetJS로 읽는 중...`);
|
console.log(`📊 ${isXLS ? "XLS" : "XLSX"} 파일을 SheetJS로 읽는 중...`);
|
||||||
workbook = XLSX.read(arrayBuffer, {
|
workbook = XLSX.read(arrayBuffer);
|
||||||
type: "array",
|
|
||||||
cellText: true,
|
|
||||||
sheetStubs: true,
|
|
||||||
WTF: true,
|
|
||||||
bookSheets: false,
|
|
||||||
codepage: 65001,
|
|
||||||
raw: false,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Sheets가 없고 SheetNames만 있는 경우 재시도
|
// 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) {
|
} catch (readError) {
|
||||||
console.error("❌ SheetJS 파일 읽기 실패:", readError);
|
console.error("❌ SheetJS 파일 읽기 실패:", readError);
|
||||||
@@ -522,7 +490,83 @@ async function processFileWithSheetJSToXLSX(
|
|||||||
type: "array",
|
type: "array",
|
||||||
bookType: "xlsx",
|
bookType: "xlsx",
|
||||||
compression: true,
|
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로 변환
|
// xlsxData는 Uint8Array이므로 ArrayBuffer로 변환
|
||||||
if (xlsxData instanceof Uint8Array) {
|
if (xlsxData instanceof Uint8Array) {
|
||||||
xlsxArrayBuffer = xlsxData.buffer.slice(
|
xlsxArrayBuffer = xlsxData.buffer.slice(
|
||||||
|
|||||||
Reference in New Issue
Block a user