ERP-node/frontend/lib/registry/components/pivot-grid/PivotGridRenderer.tsx

247 lines
6.5 KiB
TypeScript

"use client";
/**
* PivotGrid 렌더러
* 화면 관리 시스템에서 PivotGrid를 렌더링하는 컴포넌트
*/
import React, { useState, useEffect, useMemo } from "react";
import { ComponentRegistry } from "../../ComponentRegistry";
import { PivotGridComponent } from "./PivotGridComponent";
import { PivotGridConfigPanel } from "./PivotGridConfigPanel";
import {
PivotGridComponentConfig,
PivotFieldConfig,
PivotCellData,
} from "./types";
import { apiClient } from "@/lib/api/client";
// ==================== 타입 ====================
interface PivotGridRendererProps {
// 위젯 ID
id?: string;
// 컴포넌트 설정
config?: PivotGridComponentConfig;
// 외부 데이터 (formData 등에서 주입)
data?: Record<string, any>[];
// 화면 관리 컨텍스트
formData?: Record<string, any>;
// 이벤트 핸들러
onCellClick?: (cellData: PivotCellData) => void;
onDataLoad?: (data: Record<string, any>[]) => void;
// 제어관리 연동
buttonControlOptions?: {
buttonId?: string;
actionType?: string;
};
// 자동 필터 (멀티테넌시)
autoFilter?: {
companyCode?: string;
};
}
// ==================== 메인 컴포넌트 ====================
export const PivotGridRenderer: React.FC<PivotGridRendererProps> = ({
id,
config,
data: externalData,
formData,
onCellClick,
onDataLoad,
buttonControlOptions,
autoFilter,
}) => {
const [data, setData] = useState<Record<string, any>[]>([]);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
// 데이터 로드
useEffect(() => {
const loadData = async () => {
// 외부 데이터가 있으면 사용
if (externalData && externalData.length > 0) {
setData(externalData);
onDataLoad?.(externalData);
return;
}
// 데이터 소스 설정 확인
if (!config?.dataSource?.tableName) {
setData([]);
return;
}
setLoading(true);
setError(null);
try {
// 테이블 데이터 조회
const params: any = {
tableName: config.dataSource.tableName,
};
// 멀티테넌시 필터 적용
if (autoFilter?.companyCode) {
params.companyCode = autoFilter.companyCode;
}
// 필터 조건 적용
if (config.dataSource.filterConditions) {
const filters: Record<string, any> = {};
config.dataSource.filterConditions.forEach((cond) => {
if (cond.valueFromField && formData) {
filters[cond.field] = formData[cond.valueFromField];
} else if (cond.value !== undefined) {
filters[cond.field] = cond.value;
}
});
params.filters = JSON.stringify(filters);
}
const response = await apiClient.get(
`/api/table-management/data/${config.dataSource.tableName}`,
{ params }
);
if (response.data.success) {
const loadedData = response.data.data || [];
setData(loadedData);
onDataLoad?.(loadedData);
} else {
throw new Error(response.data.message || "데이터 로드 실패");
}
} catch (err: any) {
console.error("PivotGrid 데이터 로드 실패:", err);
setError(err.message || "데이터를 불러오는데 실패했습니다");
setData([]);
} finally {
setLoading(false);
}
};
loadData();
}, [
config?.dataSource?.tableName,
config?.dataSource?.filterConditions,
externalData,
formData,
autoFilter?.companyCode,
onDataLoad,
]);
// 필드 설정에서 formData 값 적용
const processedFields = useMemo<PivotFieldConfig[]>(() => {
if (!config?.fields) return [];
return config.fields.map((field) => {
// 필터 값에 formData 적용
if (field.filterValues && formData) {
return {
...field,
filterValues: field.filterValues.map((v) => {
if (typeof v === "string" && v.startsWith("{{") && v.endsWith("}}")) {
const key = v.slice(2, -2).trim();
return formData[key] ?? v;
}
return v;
}),
};
}
return field;
});
}, [config?.fields, formData]);
// 로딩 상태
if (loading) {
return (
<div className="flex items-center justify-center p-8 text-muted-foreground">
<div className="flex items-center gap-2">
<div className="h-4 w-4 animate-spin rounded-full border-2 border-primary border-t-transparent" />
<span className="text-sm"> ...</span>
</div>
</div>
);
}
// 에러 상태
if (error) {
return (
<div className="flex items-center justify-center p-8 text-destructive">
<div className="text-center">
<p className="text-sm font-medium"> </p>
<p className="text-xs mt-1">{error}</p>
</div>
</div>
);
}
return (
<PivotGridComponent
id={id}
title={config?.dataSource?.tableName}
data={data}
fields={processedFields}
totals={config?.totals}
style={config?.style}
fieldChooser={config?.fieldChooser}
chart={config?.chart}
allowSortingBySummary={config?.allowSortingBySummary}
allowFiltering={config?.allowFiltering}
allowExpandAll={config?.allowExpandAll}
wordWrapEnabled={config?.wordWrapEnabled}
height={config?.height}
maxHeight={config?.maxHeight}
exportConfig={config?.exportConfig}
onCellClick={onCellClick}
/>
);
};
// ==================== 컴포넌트 등록 ====================
ComponentRegistry.register({
type: "pivot-grid",
label: "피벗 그리드",
category: "data",
icon: "BarChart3",
description: "다차원 데이터 분석을 위한 피벗 테이블 컴포넌트",
defaultConfig: {
dataSource: {
type: "table",
tableName: "",
},
fields: [],
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",
},
Renderer: PivotGridRenderer,
ConfigPanel: PivotGridConfigPanel,
});
export default PivotGridRenderer;