Merge branch 'main' of http://39.117.244.52:3000/kjs/ERP-node
This commit is contained in:
commit
08de1372c5
|
|
@ -254,7 +254,10 @@ class DataService {
|
||||||
key !== "limit" &&
|
key !== "limit" &&
|
||||||
key !== "offset" &&
|
key !== "offset" &&
|
||||||
key !== "orderBy" &&
|
key !== "orderBy" &&
|
||||||
key !== "userLang"
|
key !== "userLang" &&
|
||||||
|
key !== "page" &&
|
||||||
|
key !== "pageSize" &&
|
||||||
|
key !== "size"
|
||||||
) {
|
) {
|
||||||
// 컬럼명 검증 (SQL 인젝션 방지)
|
// 컬럼명 검증 (SQL 인젝션 방지)
|
||||||
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key)) {
|
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key)) {
|
||||||
|
|
|
||||||
|
|
@ -184,7 +184,7 @@ const DataCell: React.FC<DataCellProps> = ({
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
onDoubleClick={onDoubleClick}
|
onDoubleClick={onDoubleClick}
|
||||||
>
|
>
|
||||||
-
|
0
|
||||||
</td>
|
</td>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -222,7 +222,7 @@ const DataCell: React.FC<DataCellProps> = ({
|
||||||
)}
|
)}
|
||||||
<span className="relative z-10 flex items-center justify-end gap-1">
|
<span className="relative z-10 flex items-center justify-end gap-1">
|
||||||
{icon && <span>{icon}</span>}
|
{icon && <span>{icon}</span>}
|
||||||
{values[0].formattedValue}
|
{values[0].formattedValue || (values[0].value === 0 ? '0' : values[0].formattedValue)}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
);
|
);
|
||||||
|
|
@ -257,7 +257,7 @@ const DataCell: React.FC<DataCellProps> = ({
|
||||||
)}
|
)}
|
||||||
<span className="relative z-10 flex items-center justify-end gap-1">
|
<span className="relative z-10 flex items-center justify-end gap-1">
|
||||||
{icon && <span>{icon}</span>}
|
{icon && <span>{icon}</span>}
|
||||||
{val.formattedValue}
|
{val.formattedValue || (val.value === 0 ? '0' : val.formattedValue)}
|
||||||
</span>
|
</span>
|
||||||
</td>
|
</td>
|
||||||
))}
|
))}
|
||||||
|
|
@ -303,6 +303,17 @@ export const PivotGridComponent: React.FC<PivotGridProps> = ({
|
||||||
externalDataLength: externalData?.length,
|
externalDataLength: externalData?.length,
|
||||||
initialFieldsLength: initialFields?.length,
|
initialFieldsLength: initialFields?.length,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 🆕 데이터 샘플 확인
|
||||||
|
if (externalData && externalData.length > 0) {
|
||||||
|
console.log("🔶 첫 번째 데이터 샘플:", externalData[0]);
|
||||||
|
console.log("🔶 전체 데이터 개수:", externalData.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 🆕 필드 설정 확인
|
||||||
|
if (initialFields && initialFields.length > 0) {
|
||||||
|
console.log("🔶 필드 설정:", initialFields);
|
||||||
|
}
|
||||||
// ==================== 상태 ====================
|
// ==================== 상태 ====================
|
||||||
|
|
||||||
const [fields, setFields] = useState<PivotFieldConfig[]>(initialFields);
|
const [fields, setFields] = useState<PivotFieldConfig[]>(initialFields);
|
||||||
|
|
@ -312,6 +323,9 @@ export const PivotGridComponent: React.FC<PivotGridProps> = ({
|
||||||
sortConfig: null,
|
sortConfig: null,
|
||||||
filterConfig: {},
|
filterConfig: {},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// 🆕 초기 로드 시 자동 확장 (첫 레벨만)
|
||||||
|
const [isInitialExpanded, setIsInitialExpanded] = useState(false);
|
||||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||||
const [showFieldPanel, setShowFieldPanel] = useState(false); // 기본적으로 접힌 상태
|
const [showFieldPanel, setShowFieldPanel] = useState(false); // 기본적으로 접힌 상태
|
||||||
const [showFieldChooser, setShowFieldChooser] = useState(false);
|
const [showFieldChooser, setShowFieldChooser] = useState(false);
|
||||||
|
|
@ -494,14 +508,53 @@ export const PivotGridComponent: React.FC<PivotGridProps> = ({
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return processPivotData(
|
const result = processPivotData(
|
||||||
filteredData,
|
filteredData,
|
||||||
visibleFields,
|
visibleFields,
|
||||||
pivotState.expandedRowPaths,
|
pivotState.expandedRowPaths,
|
||||||
pivotState.expandedColumnPaths
|
pivotState.expandedColumnPaths
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 🆕 피벗 결과 확인
|
||||||
|
console.log("🔶 피벗 처리 결과:", {
|
||||||
|
hasResult: !!result,
|
||||||
|
flatRowsCount: result?.flatRows?.length,
|
||||||
|
flatColumnsCount: result?.flatColumns?.length,
|
||||||
|
dataMatrixSize: result?.dataMatrix?.size,
|
||||||
|
expandedRowPaths: pivotState.expandedRowPaths.length,
|
||||||
|
expandedColumnPaths: pivotState.expandedColumnPaths.length,
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
}, [filteredData, fields, pivotState.expandedRowPaths, pivotState.expandedColumnPaths]);
|
}, [filteredData, fields, pivotState.expandedRowPaths, pivotState.expandedColumnPaths]);
|
||||||
|
|
||||||
|
// 🆕 초기 로드 시 첫 레벨 자동 확장
|
||||||
|
useEffect(() => {
|
||||||
|
if (pivotResult && pivotResult.flatRows.length > 0) {
|
||||||
|
console.log("🔶 피벗 결과 생성됨:", {
|
||||||
|
flatRowsCount: pivotResult.flatRows.length,
|
||||||
|
expandedRowPaths: pivotState.expandedRowPaths.length,
|
||||||
|
isInitialExpanded,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 첫 레벨 행들의 경로 수집 (level 0인 행들)
|
||||||
|
const firstLevelRows = pivotResult.flatRows.filter(row => row.level === 0 && row.hasChildren);
|
||||||
|
|
||||||
|
console.log("🔶 첫 레벨 행 (level 0, hasChildren):", firstLevelRows.map(r => ({ path: r.path, caption: r.caption })));
|
||||||
|
|
||||||
|
// 초기 확장이 안 되어 있고, 첫 레벨 행이 있으면 자동 확장
|
||||||
|
if (!isInitialExpanded && firstLevelRows.length > 0) {
|
||||||
|
const firstLevelPaths = firstLevelRows.map(row => row.path);
|
||||||
|
console.log("🔶 초기 자동 확장 실행:", firstLevelPaths);
|
||||||
|
setPivotState(prev => ({
|
||||||
|
...prev,
|
||||||
|
expandedRowPaths: firstLevelPaths,
|
||||||
|
}));
|
||||||
|
setIsInitialExpanded(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [pivotResult, isInitialExpanded, pivotState.expandedRowPaths.length]);
|
||||||
|
|
||||||
// 조건부 서식용 전체 값 수집
|
// 조건부 서식용 전체 값 수집
|
||||||
const allCellValues = useMemo(() => {
|
const allCellValues = useMemo(() => {
|
||||||
if (!pivotResult) return new Map<string, number[]>();
|
if (!pivotResult) return new Map<string, number[]>();
|
||||||
|
|
@ -665,6 +718,8 @@ export const PivotGridComponent: React.FC<PivotGridProps> = ({
|
||||||
// 행 확장/축소
|
// 행 확장/축소
|
||||||
const handleToggleRowExpand = useCallback(
|
const handleToggleRowExpand = useCallback(
|
||||||
(path: string[]) => {
|
(path: string[]) => {
|
||||||
|
console.log("🔶 행 확장/축소 클릭:", path);
|
||||||
|
|
||||||
setPivotState((prev) => {
|
setPivotState((prev) => {
|
||||||
const pathKey = pathToKey(path);
|
const pathKey = pathToKey(path);
|
||||||
const existingIndex = prev.expandedRowPaths.findIndex(
|
const existingIndex = prev.expandedRowPaths.findIndex(
|
||||||
|
|
@ -673,13 +728,16 @@ export const PivotGridComponent: React.FC<PivotGridProps> = ({
|
||||||
|
|
||||||
let newPaths: string[][];
|
let newPaths: string[][];
|
||||||
if (existingIndex >= 0) {
|
if (existingIndex >= 0) {
|
||||||
|
console.log("🔶 행 축소:", path);
|
||||||
newPaths = prev.expandedRowPaths.filter(
|
newPaths = prev.expandedRowPaths.filter(
|
||||||
(_, i) => i !== existingIndex
|
(_, i) => i !== existingIndex
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
|
console.log("🔶 행 확장:", path);
|
||||||
newPaths = [...prev.expandedRowPaths, path];
|
newPaths = [...prev.expandedRowPaths, path];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("🔶 새로운 확장 경로:", newPaths);
|
||||||
onExpandChange?.(newPaths);
|
onExpandChange?.(newPaths);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
@ -1557,13 +1615,13 @@ export const PivotGridComponent: React.FC<PivotGridProps> = ({
|
||||||
<table ref={tableRef} className="w-full border-collapse">
|
<table ref={tableRef} className="w-full border-collapse">
|
||||||
<thead>
|
<thead>
|
||||||
{/* 열 헤더 */}
|
{/* 열 헤더 */}
|
||||||
<tr className="bg-muted/50">
|
<tr className="bg-background">
|
||||||
{/* 좌상단 코너 (행 필드 라벨 + 필터) */}
|
{/* 좌상단 코너 (행 필드 라벨 + 필터) */}
|
||||||
<th
|
<th
|
||||||
className={cn(
|
className={cn(
|
||||||
"border-r border-b border-border",
|
"border-r border-b border-border",
|
||||||
"px-2 py-2 text-left text-xs font-medium",
|
"px-2 py-1 text-left text-xs font-medium",
|
||||||
"bg-muted sticky left-0 top-0 z-20"
|
"bg-background sticky left-0 top-0 z-20"
|
||||||
)}
|
)}
|
||||||
rowSpan={columnFields.length > 0 ? 2 : 1}
|
rowSpan={columnFields.length > 0 ? 2 : 1}
|
||||||
>
|
>
|
||||||
|
|
@ -1607,8 +1665,8 @@ export const PivotGridComponent: React.FC<PivotGridProps> = ({
|
||||||
key={idx}
|
key={idx}
|
||||||
className={cn(
|
className={cn(
|
||||||
"border-r border-b border-border relative group",
|
"border-r border-b border-border relative group",
|
||||||
"px-2 py-1.5 text-center text-xs font-medium",
|
"px-2 py-1 text-center text-xs font-medium",
|
||||||
"bg-muted/70 sticky top-0 z-10",
|
"bg-background sticky top-0 z-10",
|
||||||
dataFields.length === 1 && "cursor-pointer hover:bg-accent/50"
|
dataFields.length === 1 && "cursor-pointer hover:bg-accent/50"
|
||||||
)}
|
)}
|
||||||
colSpan={dataFields.length || 1}
|
colSpan={dataFields.length || 1}
|
||||||
|
|
@ -1631,15 +1689,30 @@ export const PivotGridComponent: React.FC<PivotGridProps> = ({
|
||||||
</th>
|
</th>
|
||||||
))}
|
))}
|
||||||
|
|
||||||
{/* 열 필드 필터 (헤더 왼쪽에 표시) */}
|
{/* 행 총계 헤더 */}
|
||||||
|
{totals?.showRowGrandTotals && (
|
||||||
|
<th
|
||||||
|
className={cn(
|
||||||
|
"border-b border-border",
|
||||||
|
"px-2 py-1 text-center text-xs font-medium",
|
||||||
|
"bg-background sticky top-0 z-10"
|
||||||
|
)}
|
||||||
|
colSpan={dataFields.length || 1}
|
||||||
|
rowSpan={dataFields.length > 1 ? 2 : 1}
|
||||||
|
>
|
||||||
|
총계
|
||||||
|
</th>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* 열 필드 필터 (헤더 오른쪽 끝에 표시) */}
|
||||||
{columnFields.length > 0 && (
|
{columnFields.length > 0 && (
|
||||||
<th
|
<th
|
||||||
className={cn(
|
className={cn(
|
||||||
"border-b border-border",
|
"border-b border-border",
|
||||||
"px-1 py-1.5 text-center text-xs",
|
"px-1 py-1 text-center text-xs",
|
||||||
"bg-muted/50 sticky top-0 z-10"
|
"bg-background sticky top-0 z-10"
|
||||||
)}
|
)}
|
||||||
rowSpan={columnFields.length > 0 ? 2 : 1}
|
rowSpan={dataFields.length > 1 ? 2 : 1}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col gap-0.5">
|
<div className="flex flex-col gap-0.5">
|
||||||
{columnFields.map((f) => (
|
{columnFields.map((f) => (
|
||||||
|
|
@ -1671,25 +1744,11 @@ export const PivotGridComponent: React.FC<PivotGridProps> = ({
|
||||||
</div>
|
</div>
|
||||||
</th>
|
</th>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* 행 총계 헤더 */}
|
|
||||||
{totals?.showRowGrandTotals && (
|
|
||||||
<th
|
|
||||||
className={cn(
|
|
||||||
"border-b border-border",
|
|
||||||
"px-2 py-1.5 text-center text-xs font-medium",
|
|
||||||
"bg-primary/10 sticky top-0 z-10"
|
|
||||||
)}
|
|
||||||
colSpan={dataFields.length || 1}
|
|
||||||
>
|
|
||||||
총계
|
|
||||||
</th>
|
|
||||||
)}
|
|
||||||
</tr>
|
</tr>
|
||||||
|
|
||||||
{/* 데이터 필드 라벨 (다중 데이터 필드인 경우) */}
|
{/* 데이터 필드 라벨 (다중 데이터 필드인 경우) */}
|
||||||
{dataFields.length > 1 && (
|
{dataFields.length > 1 && (
|
||||||
<tr className="bg-muted/30">
|
<tr className="bg-background">
|
||||||
{flatColumns.map((col, colIdx) => (
|
{flatColumns.map((col, colIdx) => (
|
||||||
<React.Fragment key={colIdx}>
|
<React.Fragment key={colIdx}>
|
||||||
{dataFields.map((df, dfIdx) => (
|
{dataFields.map((df, dfIdx) => (
|
||||||
|
|
@ -1697,7 +1756,7 @@ export const PivotGridComponent: React.FC<PivotGridProps> = ({
|
||||||
key={`${colIdx}-${dfIdx}`}
|
key={`${colIdx}-${dfIdx}`}
|
||||||
className={cn(
|
className={cn(
|
||||||
"border-r border-b border-border",
|
"border-r border-b border-border",
|
||||||
"px-2 py-1 text-center text-xs font-normal",
|
"px-2 py-0.5 text-center text-xs font-normal",
|
||||||
"text-muted-foreground cursor-pointer hover:bg-accent/50"
|
"text-muted-foreground cursor-pointer hover:bg-accent/50"
|
||||||
)}
|
)}
|
||||||
onClick={() => handleSort(df.field)}
|
onClick={() => handleSort(df.field)}
|
||||||
|
|
@ -1710,19 +1769,6 @@ export const PivotGridComponent: React.FC<PivotGridProps> = ({
|
||||||
))}
|
))}
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
))}
|
))}
|
||||||
{totals?.showRowGrandTotals &&
|
|
||||||
dataFields.map((df, dfIdx) => (
|
|
||||||
<th
|
|
||||||
key={`total-${dfIdx}`}
|
|
||||||
className={cn(
|
|
||||||
"border-r border-b border-border",
|
|
||||||
"px-2 py-1 text-center text-xs font-normal",
|
|
||||||
"bg-primary/5 text-muted-foreground"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{df.caption}
|
|
||||||
</th>
|
|
||||||
))}
|
|
||||||
</tr>
|
</tr>
|
||||||
)}
|
)}
|
||||||
</thead>
|
</thead>
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,13 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { AutoRegisteringComponentRenderer } from "../../AutoRegisteringComponentRenderer";
|
import { AutoRegisteringComponentRenderer } from "../../AutoRegisteringComponentRenderer";
|
||||||
import { createComponentDefinition } from "../../utils/createComponentDefinition";
|
import { createComponentDefinition } from "../../utils/createComponentDefinition";
|
||||||
import { ComponentCategory } from "@/types/component";
|
import { ComponentCategory } from "@/types/component";
|
||||||
import { PivotGridComponent } from "./PivotGridComponent";
|
import { PivotGridComponent } from "./PivotGridComponent";
|
||||||
import { PivotGridConfigPanel } from "./PivotGridConfigPanel";
|
import { PivotGridConfigPanel } from "./PivotGridConfigPanel";
|
||||||
import { PivotFieldConfig } from "./types";
|
import { PivotFieldConfig } from "./types";
|
||||||
|
import { dataApi } from "@/lib/api/data";
|
||||||
|
|
||||||
// ==================== 샘플 데이터 (미리보기용) ====================
|
// ==================== 샘플 데이터 (미리보기용) ====================
|
||||||
|
|
||||||
|
|
@ -95,6 +96,48 @@ const PivotGridWrapper: React.FC<any> = (props) => {
|
||||||
const configFields = componentConfig.fields || props.fields;
|
const configFields = componentConfig.fields || props.fields;
|
||||||
const configData = props.data;
|
const configData = props.data;
|
||||||
|
|
||||||
|
// 🆕 테이블에서 데이터 자동 로딩
|
||||||
|
const [loadedData, setLoadedData] = useState<any[]>([]);
|
||||||
|
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:", {
|
console.log("🔷 PivotGridWrapper props:", {
|
||||||
isDesignMode: props.isDesignMode,
|
isDesignMode: props.isDesignMode,
|
||||||
|
|
@ -103,23 +146,28 @@ const PivotGridWrapper: React.FC<any> = (props) => {
|
||||||
hasConfig: !!props.config,
|
hasConfig: !!props.config,
|
||||||
hasData: !!configData,
|
hasData: !!configData,
|
||||||
dataLength: configData?.length,
|
dataLength: configData?.length,
|
||||||
|
hasLoadedData: loadedData.length > 0,
|
||||||
|
loadedDataLength: loadedData.length,
|
||||||
hasFields: !!configFields,
|
hasFields: !!configFields,
|
||||||
fieldsLength: configFields?.length,
|
fieldsLength: configFields?.length,
|
||||||
|
isLoading,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 디자인 모드 판단:
|
// 디자인 모드 판단:
|
||||||
// 1. isDesignMode === true
|
// 1. isDesignMode === true
|
||||||
// 2. isInteractive === false (편집 모드)
|
// 2. isInteractive === false (편집 모드)
|
||||||
// 3. 데이터가 없는 경우
|
|
||||||
const isDesignMode = props.isDesignMode === true || props.isInteractive === false;
|
const isDesignMode = props.isDesignMode === true || props.isInteractive === false;
|
||||||
const hasValidData = configData && Array.isArray(configData) && configData.length > 0;
|
|
||||||
|
// 🆕 실제 데이터 우선순위: 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 hasValidFields = configFields && Array.isArray(configFields) && configFields.length > 0;
|
||||||
|
|
||||||
// 디자인 모드이거나 데이터가 없으면 샘플 데이터 사용
|
// 디자인 모드이거나 데이터가 없으면 샘플 데이터 사용
|
||||||
const usePreviewData = isDesignMode || !hasValidData;
|
const usePreviewData = isDesignMode || (!hasValidData && !isLoading);
|
||||||
|
|
||||||
// 최종 데이터/필드 결정
|
// 최종 데이터/필드 결정
|
||||||
const finalData = usePreviewData ? SAMPLE_DATA : configData;
|
const finalData = usePreviewData ? SAMPLE_DATA : actualData;
|
||||||
const finalFields = hasValidFields ? configFields : SAMPLE_FIELDS;
|
const finalFields = hasValidFields ? configFields : SAMPLE_FIELDS;
|
||||||
const finalTitle = usePreviewData
|
const finalTitle = usePreviewData
|
||||||
? (componentConfig.title || props.title || "피벗 그리드") + " (미리보기)"
|
? (componentConfig.title || props.title || "피벗 그리드") + " (미리보기)"
|
||||||
|
|
@ -140,6 +188,18 @@ const PivotGridWrapper: React.FC<any> = (props) => {
|
||||||
showColumnTotals: true,
|
showColumnTotals: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 🆕 로딩 중 표시
|
||||||
|
if (isLoading) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center h-64 bg-muted/30 rounded-lg">
|
||||||
|
<div className="text-center space-y-2">
|
||||||
|
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto"></div>
|
||||||
|
<p className="text-sm text-muted-foreground">데이터 로딩 중...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PivotGridComponent
|
<PivotGridComponent
|
||||||
title={finalTitle}
|
title={finalTitle}
|
||||||
|
|
@ -150,7 +210,7 @@ const PivotGridWrapper: React.FC<any> = (props) => {
|
||||||
fieldChooser={componentConfig.fieldChooser || props.fieldChooser}
|
fieldChooser={componentConfig.fieldChooser || props.fieldChooser}
|
||||||
chart={componentConfig.chart || props.chart}
|
chart={componentConfig.chart || props.chart}
|
||||||
allowExpandAll={componentConfig.allowExpandAll !== false}
|
allowExpandAll={componentConfig.allowExpandAll !== false}
|
||||||
height={componentConfig.height || props.height || "400px"}
|
height="100%"
|
||||||
maxHeight={componentConfig.maxHeight || props.maxHeight}
|
maxHeight={componentConfig.maxHeight || props.maxHeight}
|
||||||
exportConfig={componentConfig.exportConfig || props.exportConfig || { excel: true }}
|
exportConfig={componentConfig.exportConfig || props.exportConfig || { excel: true }}
|
||||||
onCellClick={props.onCellClick}
|
onCellClick={props.onCellClick}
|
||||||
|
|
@ -279,7 +339,7 @@ export class PivotGridRenderer extends AutoRegisteringComponentRenderer {
|
||||||
fieldChooser={componentConfig.fieldChooser || props.fieldChooser}
|
fieldChooser={componentConfig.fieldChooser || props.fieldChooser}
|
||||||
chart={componentConfig.chart || props.chart}
|
chart={componentConfig.chart || props.chart}
|
||||||
allowExpandAll={componentConfig.allowExpandAll !== false}
|
allowExpandAll={componentConfig.allowExpandAll !== false}
|
||||||
height={componentConfig.height || props.height || "400px"}
|
height="100%"
|
||||||
maxHeight={componentConfig.maxHeight || props.maxHeight}
|
maxHeight={componentConfig.maxHeight || props.maxHeight}
|
||||||
exportConfig={componentConfig.exportConfig || props.exportConfig || { excel: true }}
|
exportConfig={componentConfig.exportConfig || props.exportConfig || { excel: true }}
|
||||||
onCellClick={props.onCellClick}
|
onCellClick={props.onCellClick}
|
||||||
|
|
|
||||||
|
|
@ -401,7 +401,7 @@ export const FieldChooser: React.FC<FieldChooserProps> = ({
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 필드 목록 */}
|
{/* 필드 목록 */}
|
||||||
<ScrollArea className="flex-1 -mx-6 px-6">
|
<ScrollArea className="flex-1 -mx-6 px-6 max-h-[40vh] overflow-y-auto">
|
||||||
<div className="space-y-2 py-2">
|
<div className="space-y-2 py-2">
|
||||||
{filteredFields.length === 0 ? (
|
{filteredFields.length === 0 ? (
|
||||||
<div className="text-center py-8 text-muted-foreground">
|
<div className="text-center py-8 text-muted-foreground">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue