"use client"; import React, { useEffect, useState } from "react"; import { AutoRegisteringComponentRenderer } from "../../AutoRegisteringComponentRenderer"; import { createComponentDefinition } from "../../utils/createComponentDefinition"; import { ComponentCategory } from "@/types/component"; import { PivotGridComponent } from "./PivotGridComponent"; import { PivotGridConfigPanel } from "./PivotGridConfigPanel"; import { PivotFieldConfig } from "./types"; import { dataApi } from "@/lib/api/data"; // ==================== 샘플 데이터 (미리보기용) ==================== const SAMPLE_DATA = [ { region: "서울", product: "노트북", quarter: "Q1", sales: 1500000, quantity: 15 }, { region: "서울", product: "노트북", quarter: "Q2", sales: 1800000, quantity: 18 }, { region: "서울", product: "노트북", quarter: "Q3", sales: 2100000, quantity: 21 }, { region: "서울", product: "노트북", quarter: "Q4", sales: 2500000, quantity: 25 }, { region: "서울", product: "스마트폰", quarter: "Q1", sales: 2000000, quantity: 40 }, { region: "서울", product: "스마트폰", quarter: "Q2", sales: 2200000, quantity: 44 }, { region: "서울", product: "스마트폰", quarter: "Q3", sales: 2500000, quantity: 50 }, { region: "서울", product: "스마트폰", quarter: "Q4", sales: 3000000, quantity: 60 }, { region: "서울", product: "태블릿", quarter: "Q1", sales: 800000, quantity: 10 }, { region: "서울", product: "태블릿", quarter: "Q2", sales: 900000, quantity: 11 }, { region: "서울", product: "태블릿", quarter: "Q3", sales: 1000000, quantity: 12 }, { region: "서울", product: "태블릿", quarter: "Q4", sales: 1200000, quantity: 15 }, { region: "부산", product: "노트북", quarter: "Q1", sales: 1000000, quantity: 10 }, { region: "부산", product: "노트북", quarter: "Q2", sales: 1200000, quantity: 12 }, { region: "부산", product: "노트북", quarter: "Q3", sales: 1400000, quantity: 14 }, { region: "부산", product: "노트북", quarter: "Q4", sales: 1600000, quantity: 16 }, { region: "부산", product: "스마트폰", quarter: "Q1", sales: 1500000, quantity: 30 }, { region: "부산", product: "스마트폰", quarter: "Q2", sales: 1700000, quantity: 34 }, { region: "부산", product: "스마트폰", quarter: "Q3", sales: 1900000, quantity: 38 }, { region: "부산", product: "스마트폰", quarter: "Q4", sales: 2200000, quantity: 44 }, { region: "부산", product: "태블릿", quarter: "Q1", sales: 500000, quantity: 6 }, { region: "부산", product: "태블릿", quarter: "Q2", sales: 600000, quantity: 7 }, { region: "부산", product: "태블릿", quarter: "Q3", sales: 700000, quantity: 8 }, { region: "부산", product: "태블릿", quarter: "Q4", sales: 800000, quantity: 10 }, { region: "대구", product: "노트북", quarter: "Q1", sales: 700000, quantity: 7 }, { region: "대구", product: "노트북", quarter: "Q2", sales: 850000, quantity: 8 }, { region: "대구", product: "노트북", quarter: "Q3", sales: 900000, quantity: 9 }, { region: "대구", product: "노트북", quarter: "Q4", sales: 1100000, quantity: 11 }, { region: "대구", product: "스마트폰", quarter: "Q1", sales: 1000000, quantity: 20 }, { region: "대구", product: "스마트폰", quarter: "Q2", sales: 1200000, quantity: 24 }, { region: "대구", product: "스마트폰", quarter: "Q3", sales: 1300000, quantity: 26 }, { region: "대구", product: "스마트폰", quarter: "Q4", sales: 1500000, quantity: 30 }, { region: "대구", product: "태블릿", quarter: "Q1", sales: 400000, quantity: 5 }, { region: "대구", product: "태블릿", quarter: "Q2", sales: 450000, quantity: 5 }, { region: "대구", product: "태블릿", quarter: "Q3", sales: 500000, quantity: 6 }, { region: "대구", product: "태블릿", quarter: "Q4", sales: 600000, quantity: 7 }, ]; const SAMPLE_FIELDS: PivotFieldConfig[] = [ { field: "region", caption: "지역", area: "row", areaIndex: 0, dataType: "string", visible: true, }, { field: "product", caption: "제품", area: "row", areaIndex: 1, dataType: "string", visible: true, }, { field: "quarter", caption: "분기", area: "column", areaIndex: 0, dataType: "string", visible: true, }, { field: "sales", caption: "매출", area: "data", areaIndex: 0, dataType: "number", summaryType: "sum", format: { type: "number", precision: 0 }, visible: true, }, ]; /** * PivotGrid 래퍼 컴포넌트 (디자인 모드에서 샘플 데이터 주입) */ const PivotGridWrapper: React.FC = (props) => { // 컴포넌트 설정에서 값 추출 const componentConfig = props.componentConfig || props.config || {}; const configFields = componentConfig.fields || props.fields; const configData = props.data; // 🆕 테이블에서 데이터 자동 로딩 const [loadedData, setLoadedData] = useState([]); const [isLoading, setIsLoading] = useState(false); useEffect(() => { const loadTableData = async () => { const tableName = componentConfig.dataSource?.tableName; // 데이터가 이미 있거나, 테이블명이 없으면 로딩하지 않음 if (configData || !tableName || props.isDesignMode) { return; } setIsLoading(true); try { console.log("🔷 [PivotGrid] 테이블 데이터 로딩 시작:", tableName); const response = await dataApi.getTableData(tableName, { page: 1, size: 10000, // 피벗 분석용 대량 데이터 (pageSize → size) }); console.log("🔷 [PivotGrid] API 응답:", response); // dataApi.getTableData는 { data, total, page, size, totalPages } 구조 if (response.data && Array.isArray(response.data)) { setLoadedData(response.data); console.log("✅ [PivotGrid] 데이터 로딩 완료:", response.data.length, "건"); } else { console.error("❌ [PivotGrid] 데이터 로딩 실패: 응답에 data 배열이 없음"); setLoadedData([]); } } catch (error) { console.error("❌ [PivotGrid] 데이터 로딩 에러:", error); } finally { setIsLoading(false); } }; loadTableData(); }, [componentConfig.dataSource?.tableName, configData, props.isDesignMode]); // 디버깅 로그 console.log("🔷 PivotGridWrapper props:", { isDesignMode: props.isDesignMode, isInteractive: props.isInteractive, hasComponentConfig: !!props.componentConfig, hasConfig: !!props.config, hasData: !!configData, dataLength: configData?.length, hasLoadedData: loadedData.length > 0, loadedDataLength: loadedData.length, hasFields: !!configFields, fieldsLength: configFields?.length, isLoading, }); // 디자인 모드 판단: // 1. isDesignMode === true // 2. isInteractive === false (편집 모드) const isDesignMode = props.isDesignMode === true || props.isInteractive === false; // 🆕 실제 데이터 우선순위: props.data > loadedData > 샘플 데이터 const actualData = configData || loadedData; const hasValidData = actualData && Array.isArray(actualData) && actualData.length > 0; const hasValidFields = configFields && Array.isArray(configFields) && configFields.length > 0; // 디자인 모드이거나 데이터가 없으면 샘플 데이터 사용 const usePreviewData = isDesignMode || (!hasValidData && !isLoading); // 최종 데이터/필드 결정 const finalData = usePreviewData ? SAMPLE_DATA : actualData; const finalFields = hasValidFields ? configFields : SAMPLE_FIELDS; const finalTitle = usePreviewData ? (componentConfig.title || props.title || "피벗 그리드") + " (미리보기)" : (componentConfig.title || props.title); console.log("🔷 PivotGridWrapper final:", { isDesignMode, usePreviewData, finalDataLength: finalData?.length, finalFieldsLength: finalFields?.length, }); // 총계 설정 const totalsConfig = componentConfig.totals || props.totals || { showRowGrandTotals: true, showColumnGrandTotals: true, showRowTotals: true, showColumnTotals: true, }; // 🆕 로딩 중 표시 if (isLoading) { return (

데이터 로딩 중...

); } return ( ); }; /** * PivotGrid 컴포넌트 정의 */ const PivotGridDefinition = createComponentDefinition({ id: "pivot-grid", name: "피벗 그리드", nameEng: "PivotGrid Component", description: "다차원 데이터 분석을 위한 피벗 테이블 컴포넌트", category: ComponentCategory.DISPLAY, webType: "text", component: PivotGridWrapper, // 래퍼 컴포넌트 사용 defaultConfig: { dataSource: { type: "table", tableName: "", }, fields: SAMPLE_FIELDS, // 미리보기용 샘플 데이터 sampleData: SAMPLE_DATA, totals: { showRowGrandTotals: true, showColumnGrandTotals: true, showRowTotals: true, showColumnTotals: true, }, style: { theme: "default", headerStyle: "default", cellPadding: "normal", borderStyle: "light", alternateRowColors: true, highlightTotals: true, }, allowExpandAll: true, exportConfig: { excel: true, }, height: "400px", }, defaultSize: { width: 800, height: 500 }, configPanel: PivotGridConfigPanel, icon: "BarChart3", tags: ["피벗", "분석", "집계", "그리드", "데이터"], version: "1.0.0", author: "개발팀", documentation: "", }); /** * PivotGrid 렌더러 * 자동 등록 시스템을 사용하여 컴포넌트를 레지스트리에 등록 */ export class PivotGridRenderer extends AutoRegisteringComponentRenderer { static componentDefinition = PivotGridDefinition; render(): React.ReactElement { const props = this.props as any; // 컴포넌트 설정에서 값 추출 const componentConfig = props.componentConfig || props.config || {}; const configFields = componentConfig.fields || props.fields; const configData = props.data; // 디버깅 로그 console.log("🔷 PivotGridRenderer props:", { isDesignMode: props.isDesignMode, isInteractive: props.isInteractive, hasComponentConfig: !!props.componentConfig, hasConfig: !!props.config, hasData: !!configData, dataLength: configData?.length, hasFields: !!configFields, fieldsLength: configFields?.length, }); // 디자인 모드 판단: // 1. isDesignMode === true // 2. isInteractive === false (편집 모드) // 3. 데이터가 없는 경우 const isDesignMode = props.isDesignMode === true || props.isInteractive === false; const hasValidData = configData && Array.isArray(configData) && configData.length > 0; const hasValidFields = configFields && Array.isArray(configFields) && configFields.length > 0; // 디자인 모드이거나 데이터가 없으면 샘플 데이터 사용 const usePreviewData = isDesignMode || !hasValidData; // 최종 데이터/필드 결정 const finalData = usePreviewData ? SAMPLE_DATA : configData; const finalFields = hasValidFields ? configFields : SAMPLE_FIELDS; const finalTitle = usePreviewData ? (componentConfig.title || props.title || "피벗 그리드") + " (미리보기)" : (componentConfig.title || props.title); console.log("🔷 PivotGridRenderer final:", { isDesignMode, usePreviewData, finalDataLength: finalData?.length, finalFieldsLength: finalFields?.length, }); // 총계 설정 const totalsConfig = componentConfig.totals || props.totals || { showRowGrandTotals: true, showColumnGrandTotals: true, showRowTotals: true, showColumnTotals: true, }; return ( ); } } // 자동 등록 실행 PivotGridRenderer.registerSelf(); // 강제 등록 (디버깅용) if (typeof window !== "undefined") { setTimeout(() => { try { PivotGridRenderer.registerSelf(); } catch (error) { console.error("❌ PivotGrid 강제 등록 실패:", error); } }, 1000); }