"use client";
import React, { useEffect, useState, Component, ErrorInfo, ReactNode } 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";
import { AlertCircle, RefreshCw } from "lucide-react";
import { Button } from "@/components/ui/button";
// ==================== ์๋ฌ ๊ฒฝ๊ณ ====================
interface ErrorBoundaryState {
hasError: boolean;
error?: Error;
}
class PivotGridErrorBoundary extends Component<
{ children: ReactNode; onReset?: () => void },
ErrorBoundaryState
> {
constructor(props: { children: ReactNode; onReset?: () => void }) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return { hasError: true, error };
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
console.error("๐ด [PivotGrid] ๋ ๋๋ง ์๋ฌ:", error);
console.error("๐ด [PivotGrid] ์๋ฌ ์ ๋ณด:", errorInfo);
}
handleReset = () => {
this.setState({ hasError: false, error: undefined });
this.props.onReset?.();
};
render() {
if (this.state.hasError) {
return (
ํผ๋ฒ ๊ทธ๋ฆฌ๋ ์ค๋ฅ
{this.state.error?.message || "์ ์ ์๋ ์ค๋ฅ๊ฐ ๋ฐ์ํ์ต๋๋ค."}
);
}
return this.props.children;
}
}
// ==================== ์ํ ๋ฐ์ดํฐ (๋ฏธ๋ฆฌ๋ณด๊ธฐ์ฉ) ====================
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 {
const response = await dataApi.getTableData(tableName, {
page: 1,
size: 10000, // ํผ๋ฒ ๋ถ์์ฉ ๋๋ ๋ฐ์ดํฐ
});
// dataApi.getTableData๋ { data, total, page, size, totalPages } ๊ตฌ์กฐ
if (response.data && Array.isArray(response.data)) {
setLoadedData(response.data);
} else {
console.error("โ [PivotGrid] ๋ฐ์ดํฐ ๋ก๋ฉ ์คํจ: ์๋ต์ data ๋ฐฐ์ด์ด ์์");
setLoadedData([]);
}
} catch (error) {
console.error("โ [PivotGrid] ๋ฐ์ดํฐ ๋ก๋ฉ ์๋ฌ:", error);
} finally {
setIsLoading(false);
}
};
loadTableData();
}, [componentConfig.dataSource?.tableName, configData, props.isDesignMode]);
// ๋์์ธ ๋ชจ๋ ํ๋จ:
// 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);
// ์ด๊ณ ์ค์
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;
// ๋์์ธ ๋ชจ๋ ํ๋จ:
// 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);
// ์ด๊ณ ์ค์
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);
}