feat(SplitPanelLayout2): 추가 조인 테이블 기능 구현

- JoinTableConfig 타입 정의 (joinTable, joinType, mainColumn, joinColumn, selectColumns)

- RightPanelConfig.joinTables 배열 추가로 다중 조인 지원

- loadJoinTableData(), mergeJoinData() 함수로 클라이언트 사이드 조인 처리

- JoinTableItem 컴포넌트로 조인 테이블 설정 UI 제공

- 표시 컬럼에 sourceTable 추가로 테이블별 컬럼 구분

- 메인+조인 테이블 컬럼 통합 로드 기능
This commit is contained in:
SeongHyun Kim
2025-12-05 18:15:20 +09:00
parent de1fe9865a
commit a5055cae15
3 changed files with 655 additions and 51 deletions

View File

@@ -7,6 +7,7 @@ import {
ColumnConfig,
DataTransferField,
ActionButtonConfig,
JoinTableConfig,
} from "./types";
import { defaultConfig } from "./config";
import { cn } from "@/lib/utils";
@@ -128,6 +129,94 @@ export const SplitPanelLayout2Component: React.FC<SplitPanelLayout2ComponentProp
}
}, [config.leftPanel?.tableName, config.leftPanel?.hierarchyConfig, isDesignMode]);
// 조인 테이블 데이터 로드 (단일 테이블)
const loadJoinTableData = useCallback(async (
joinConfig: JoinTableConfig,
mainData: any[]
): Promise<Map<string, any>> => {
const resultMap = new Map<string, any>();
if (!joinConfig.joinTable || !joinConfig.mainColumn || !joinConfig.joinColumn || mainData.length === 0) {
return resultMap;
}
// 메인 데이터에서 조인할 키 값들 추출
const joinKeys = [...new Set(mainData.map((item) => item[joinConfig.mainColumn]).filter(Boolean))];
if (joinKeys.length === 0) return resultMap;
try {
console.log(`[SplitPanelLayout2] 조인 테이블 로드: ${joinConfig.joinTable}, 키: ${joinKeys.length}`);
const response = await apiClient.post(`/table-management/tables/${joinConfig.joinTable}/data`, {
page: 1,
size: 1000,
// 조인 키 값들로 필터링
dataFilter: {
enabled: true,
matchType: "any", // OR 조건으로 여러 키 매칭
filters: joinKeys.map((key, idx) => ({
id: `join_key_${idx}`,
columnName: joinConfig.joinColumn,
operator: "equals",
value: String(key),
valueType: "static",
})),
},
autoFilter: {
enabled: true,
filterColumn: "company_code",
filterType: "company",
},
});
if (response.data.success) {
const joinData = response.data.data?.data || [];
// 조인 컬럼 값을 키로 하는 Map 생성
joinData.forEach((item: any) => {
const key = item[joinConfig.joinColumn];
if (key) {
resultMap.set(String(key), item);
}
});
console.log(`[SplitPanelLayout2] 조인 테이블 로드 완료: ${joinData.length}`);
}
} catch (error) {
console.error(`[SplitPanelLayout2] 조인 테이블 로드 실패 (${joinConfig.joinTable}):`, error);
}
return resultMap;
}, []);
// 메인 데이터에 조인 테이블 데이터 병합
const mergeJoinData = useCallback((
mainData: any[],
joinConfig: JoinTableConfig,
joinDataMap: Map<string, any>
): any[] => {
return mainData.map((item) => {
const joinKey = item[joinConfig.mainColumn];
const joinRow = joinDataMap.get(String(joinKey));
if (joinRow && joinConfig.selectColumns) {
// 선택된 컬럼만 병합
const mergedItem = { ...item };
joinConfig.selectColumns.forEach((col) => {
// alias가 있으면 alias_컬럼명, 없으면 그냥 컬럼명
const targetKey = joinConfig.alias ? `${joinConfig.alias}_${col}` : col;
// 메인 테이블에 같은 컬럼이 없으면 추가
if (!(col in mergedItem)) {
mergedItem[col] = joinRow[col];
} else if (joinConfig.alias) {
// 메인 테이블에 같은 컬럼이 있으면 alias로 추가
mergedItem[targetKey] = joinRow[col];
}
});
return mergedItem;
}
return item;
});
}, []);
// 우측 데이터 로드 (좌측 선택 항목 기반)
const loadRightData = useCallback(async (selectedItem: any) => {
if (!config.rightPanel?.tableName || !config.joinConfig?.leftColumn || !config.joinConfig?.rightColumn || !selectedItem) {
@@ -173,7 +262,24 @@ export const SplitPanelLayout2Component: React.FC<SplitPanelLayout2ComponentProp
if (response.data.success) {
// API 응답 구조: { success: true, data: { data: [...], total, page, ... } }
const data = response.data.data?.data || [];
let data = response.data.data?.data || [];
console.log(`[SplitPanelLayout2] 메인 데이터 로드 완료: ${data.length}`);
// 추가 조인 테이블 처리
const joinTables = config.rightPanel?.joinTables || [];
if (joinTables.length > 0 && data.length > 0) {
console.log(`[SplitPanelLayout2] 조인 테이블 처리 시작: ${joinTables.length}`);
for (const joinTableConfig of joinTables) {
const joinDataMap = await loadJoinTableData(joinTableConfig, data);
if (joinDataMap.size > 0) {
data = mergeJoinData(data, joinTableConfig, joinDataMap);
}
}
console.log(`[SplitPanelLayout2] 조인 데이터 병합 완료`);
}
setRightData(data);
console.log(`[SplitPanelLayout2] 우측 데이터 로드 완료: ${data.length}`);
} else {
@@ -196,7 +302,7 @@ export const SplitPanelLayout2Component: React.FC<SplitPanelLayout2ComponentProp
} finally {
setRightLoading(false);
}
}, [config.rightPanel?.tableName, config.joinConfig]);
}, [config.rightPanel?.tableName, config.rightPanel?.joinTables, config.joinConfig, loadJoinTableData, mergeJoinData]);
// 좌측 패널 추가 버튼 클릭
const handleLeftAddClick = useCallback(() => {