feat: 서브 테이블 조인 및 필터 관계 정보 개선

- rightPanel.columns에서 참조하는 외부 테이블 및 조인 컬럼 정보를 수집하는 로직 추가
- 서브 테이블의 조인 컬럼 참조 정보를 포함하여 시각화 개선
- 필터 관계를 선 없이 뱃지로 표시하여 겹침 방지 및 시각적 표현 개선
- TableNodeData 인터페이스에 조인 컬럼 및 참조 정보 필드 추가
- 화면 관계 흐름에서 조인 컬럼 및 필터 관계 정보 표시 기능 개선
This commit is contained in:
DDD1542
2026-01-08 16:20:51 +09:00
parent 8928d851ca
commit b8c8b31033
5 changed files with 369 additions and 46 deletions

View File

@@ -1607,7 +1607,8 @@ export const getScreenSubTables = async (req: Request, res: Response) => {
sd.table_name as main_table,
sl.properties->>'componentType' as component_type,
sl.properties->'componentConfig'->'rightPanel'->'relation' as right_panel_relation,
sl.properties->'componentConfig'->'rightPanel'->'tableName' as right_panel_table
sl.properties->'componentConfig'->'rightPanel'->'tableName' as right_panel_table,
sl.properties->'componentConfig'->'rightPanel'->'columns' as right_panel_columns
FROM screen_definitions sd
JOIN screen_layouts sl ON sd.screen_id = sl.screen_id
WHERE sd.screen_id = ANY($1)
@@ -1616,6 +1617,29 @@ export const getScreenSubTables = async (req: Request, res: Response) => {
const rightPanelResult = await pool.query(rightPanelQuery, [screenIds]);
// rightPanel.columns에서 참조되는 외부 테이블 수집 (예: customer_mng.customer_name → customer_mng)
const rightPanelJoinedTables: Map<string, Set<string>> = new Map(); // screenId_tableName → Set<참조테이블>
rightPanelResult.rows.forEach((row: any) => {
const screenId = row.screen_id;
const rightPanelTable = row.right_panel_table;
const rightPanelColumns = row.right_panel_columns;
if (rightPanelColumns && Array.isArray(rightPanelColumns)) {
rightPanelColumns.forEach((col: any) => {
const colName = col.name || col.columnName || col.field;
if (colName && colName.includes('.')) {
const refTable = colName.split('.')[0];
const key = `${screenId}_${rightPanelTable}`;
if (!rightPanelJoinedTables.has(key)) {
rightPanelJoinedTables.set(key, new Set());
}
rightPanelJoinedTables.get(key)!.add(refTable);
}
});
}
});
rightPanelResult.rows.forEach((row: any) => {
const screenId = row.screen_id;
const mainTable = row.main_table;
@@ -1626,6 +1650,10 @@ export const getScreenSubTables = async (req: Request, res: Response) => {
// relation 객체에서 테이블 및 필드 매핑 추출
const subTable = rightPanelTable || relation?.targetTable || relation?.tableName;
if (!subTable || subTable === mainTable) return;
// rightPanel.columns에서 참조하는 외부 테이블 목록
const key = `${screenId}_${subTable}`;
const joinedTables = rightPanelJoinedTables.get(key) ? Array.from(rightPanelJoinedTables.get(key)!) : [];
if (!screenSubTables[screenId]) {
screenSubTables[screenId] = {
@@ -1697,6 +1725,7 @@ export const getScreenSubTables = async (req: Request, res: Response) => {
originalRelationType: relation?.type || 'join', // 원본 relation.type ("join" | "detail")
foreignKey: relation?.foreignKey, // 디테일 테이블의 FK 컬럼
leftColumn: relation?.leftColumn, // 마스터 테이블의 선택 기준 컬럼
joinedTables: joinedTables.length > 0 ? joinedTables : undefined, // rightPanel.columns에서 참조하는 외부 테이블들
fieldMappings: fieldMappings.length > 0 ? fieldMappings : undefined,
} as any);
}
@@ -1706,6 +1735,86 @@ export const getScreenSubTables = async (req: Request, res: Response) => {
screenIds,
rightPanelCount: rightPanelResult.rows.length
});
// 5. joinedTables에 대한 FK 컬럼을 column_labels에서 조회
// rightPanelRelation에서 joinedTables가 있는 경우, 해당 테이블과 조인하는 FK 컬럼 찾기
const joinedTableFKLookups: Array<{ subTableName: string; refTable: string }> = [];
Object.values(screenSubTables).forEach((screenData: any) => {
screenData.subTables.forEach((subTable: any) => {
if (subTable.joinedTables && Array.isArray(subTable.joinedTables)) {
subTable.joinedTables.forEach((refTable: string) => {
joinedTableFKLookups.push({ subTableName: subTable.tableName, refTable });
});
}
});
});
// column_labels에서 FK 컬럼 조회 (reference_table로 조인하는 컬럼 찾기)
const joinColumnsByTable: { [key: string]: string[] } = {}; // tableName → [FK 컬럼들]
if (joinedTableFKLookups.length > 0) {
const uniqueLookups = joinedTableFKLookups.filter((item, index, self) =>
index === self.findIndex((t) => t.subTableName === item.subTableName && t.refTable === item.refTable)
);
// 각 subTable에 대해 reference_table이 일치하는 컬럼 조회
const subTableNames = [...new Set(uniqueLookups.map(l => l.subTableName))];
const refTableNames = [...new Set(uniqueLookups.map(l => l.refTable))];
const fkQuery = `
SELECT
cl.table_name,
cl.column_name,
cl.column_label,
cl.reference_table,
cl.reference_column,
tl.table_label as reference_table_label
FROM column_labels cl
LEFT JOIN table_labels tl ON cl.reference_table = tl.table_name
WHERE cl.table_name = ANY($1)
AND cl.reference_table = ANY($2)
`;
const fkResult = await pool.query(fkQuery, [subTableNames, refTableNames]);
// 참조 정보 포함 객체 배열로 저장 (한글명 포함)
const joinColumnRefsByTable: Record<string, Array<{ column: string; columnLabel: string; refTable: string; refTableLabel: string; refColumn: string }>> = {};
fkResult.rows.forEach((row: any) => {
if (!joinColumnRefsByTable[row.table_name]) {
joinColumnRefsByTable[row.table_name] = [];
}
// 중복 체크
const exists = joinColumnRefsByTable[row.table_name].some(
(ref) => ref.column === row.column_name && ref.refTable === row.reference_table
);
if (!exists) {
joinColumnRefsByTable[row.table_name].push({
column: row.column_name,
columnLabel: row.column_label || row.column_name, // 컬럼 한글명 (없으면 영문명)
refTable: row.reference_table,
refTableLabel: row.reference_table_label || row.reference_table, // 참조 테이블 한글명 (없으면 영문명)
refColumn: row.reference_column || 'id',
});
}
});
// subTables에 joinColumns (문자열 배열) 및 joinColumnRefs (참조 정보 배열) 추가
Object.values(screenSubTables).forEach((screenData: any) => {
screenData.subTables.forEach((subTable: any) => {
const refs = joinColumnRefsByTable[subTable.tableName];
if (refs) {
(subTable as any).joinColumns = refs.map(r => r.column);
(subTable as any).joinColumnRefs = refs;
}
});
});
logger.info("rightPanel joinedTables FK 조회 완료", {
lookupCount: uniqueLookups.length,
resultCount: fkResult.rows.length,
joinColumnsByTable
});
}
// 5. 모든 fieldMappings의 한글명을 column_labels에서 가져와서 적용
// 모든 테이블/컬럼 조합을 수집