feat: 화면 그룹 및 서브 테이블 관련 로직 개선

- 화면 그룹 조회 시 삭제된 화면(is_active = 'D')을 제외하도록 쿼리를 수정하였습니다.
- 화면 서브 테이블 API에서 전역 메인 테이블 목록을 수집하여, 메인 테이블과 서브 테이블의 우선순위를 적용하였습니다.
- 화면 삭제 시 연결된 화면 그룹의 관계를 해제하는 로직을 추가하였습니다.
- 화면 관계 흐름에서 연결된 화면들을 추가하는 로직을 개선하여, 그룹 모드와 개별 화면 모드에서의 동작을 명확히 하였습니다.
- 관련 문서 및 주석을 업데이트하여 새로운 기능에 대한 이해를 돕도록 하였습니다.
This commit is contained in:
DDD1542
2026-02-03 15:50:23 +09:00
parent dd1ddd6418
commit ef9f1b94ff
8 changed files with 510 additions and 64 deletions

View File

@@ -46,11 +46,13 @@ export const getScreenGroups = async (req: AuthenticatedRequest, res: Response)
const countResult = await pool.query(countQuery, params);
const total = parseInt(countResult.rows[0].total);
// 데이터 조회 (screens 배열 포함)
// 데이터 조회 (screens 배열 포함) - 삭제된 화면(is_active = 'D') 제외
const dataQuery = `
SELECT
sg.*,
(SELECT COUNT(*) FROM screen_group_screens sgs WHERE sgs.group_id = sg.id) as screen_count,
(SELECT COUNT(*) FROM screen_group_screens sgs
LEFT JOIN screen_definitions sd ON sgs.screen_id = sd.screen_id
WHERE sgs.group_id = sg.id AND sd.is_active != 'D') as screen_count,
(SELECT json_agg(
json_build_object(
'id', sgs.id,
@@ -64,6 +66,7 @@ export const getScreenGroups = async (req: AuthenticatedRequest, res: Response)
) FROM screen_group_screens sgs
LEFT JOIN screen_definitions sd ON sgs.screen_id = sd.screen_id
WHERE sgs.group_id = sg.id
AND sd.is_active != 'D'
) as screens
FROM screen_groups sg
${whereClause}
@@ -111,6 +114,7 @@ export const getScreenGroup = async (req: AuthenticatedRequest, res: Response) =
) FROM screen_group_screens sgs
LEFT JOIN screen_definitions sd ON sgs.screen_id = sd.screen_id
WHERE sgs.group_id = sg.id
AND sd.is_active != 'D'
) as screens
FROM screen_groups sg
WHERE sg.id = $1
@@ -1737,7 +1741,9 @@ export const getScreenSubTables = async (req: AuthenticatedRequest, res: Respons
});
// 4. rightPanel.relation 파싱 (split-panel-layout 등에서 사용)
// screen_layouts (v1)와 screen_layouts_v2 모두 조회
const rightPanelQuery = `
-- V1: screen_layouts에서 조회
SELECT
sd.screen_id,
sd.screen_name,
@@ -1750,6 +1756,23 @@ export const getScreenSubTables = async (req: AuthenticatedRequest, res: Respons
JOIN screen_layouts sl ON sd.screen_id = sl.screen_id
WHERE sd.screen_id = ANY($1)
AND sl.properties->'componentConfig'->'rightPanel'->'relation' IS NOT NULL
UNION ALL
-- V2: screen_layouts_v2에서 조회 (v2-split-panel-layout 컴포넌트)
SELECT
sd.screen_id,
sd.screen_name,
sd.table_name as main_table,
comp->'overrides'->>'type' as component_type,
comp->'overrides'->'rightPanel'->'relation' as right_panel_relation,
comp->'overrides'->'rightPanel'->>'tableName' as right_panel_table,
comp->'overrides'->'rightPanel'->'columns' as right_panel_columns
FROM screen_definitions sd
JOIN screen_layouts_v2 slv2 ON sd.screen_id = slv2.screen_id,
jsonb_array_elements(slv2.layout_data->'components') as comp
WHERE sd.screen_id = ANY($1)
AND comp->'overrides'->'rightPanel'->'relation' IS NOT NULL
`;
const rightPanelResult = await pool.query(rightPanelQuery, [screenIds]);
@@ -2118,9 +2141,56 @@ export const getScreenSubTables = async (req: AuthenticatedRequest, res: Respons
}))
});
// ============================================================
// 6. 전역 메인 테이블 목록 수집 (우선순위 적용용)
// ============================================================
// 메인 테이블 조건:
// 1. screen_definitions.table_name (컴포넌트 직접 연결)
// 2. v2-split-panel-layout의 rightPanel.tableName (WHERE 조건 대상)
//
// 이 목록에 있으면 서브 테이블로 분류되지 않음 (우선순위: 메인 > 서브)
const globalMainTablesQuery = `
-- 1. 모든 화면의 메인 테이블 (screen_definitions.table_name)
SELECT DISTINCT table_name as main_table
FROM screen_definitions
WHERE screen_id = ANY($1)
AND table_name IS NOT NULL
UNION
-- 2. v2-split-panel-layout의 rightPanel.tableName (WHERE 조건 대상)
-- 현재 그룹의 화면들에서 마스터-디테일로 연결된 테이블
SELECT DISTINCT comp->'overrides'->'rightPanel'->>'tableName' as main_table
FROM screen_definitions sd
JOIN screen_layouts_v2 slv2 ON sd.screen_id = slv2.screen_id,
jsonb_array_elements(slv2.layout_data->'components') as comp
WHERE sd.screen_id = ANY($1)
AND comp->'overrides'->'rightPanel'->>'tableName' IS NOT NULL
UNION
-- 3. v1 screen_layouts의 rightPanel.tableName (WHERE 조건 대상)
SELECT DISTINCT sl.properties->'componentConfig'->'rightPanel'->>'tableName' as main_table
FROM screen_definitions sd
JOIN screen_layouts sl ON sd.screen_id = sl.screen_id
WHERE sd.screen_id = ANY($1)
AND sl.properties->'componentConfig'->'rightPanel'->>'tableName' IS NOT NULL
`;
const globalMainTablesResult = await pool.query(globalMainTablesQuery, [screenIds]);
const globalMainTables = globalMainTablesResult.rows
.map((r: any) => r.main_table)
.filter((t: string) => t != null && t !== '');
logger.info("전역 메인 테이블 목록 수집 완료", {
count: globalMainTables.length,
tables: globalMainTables
});
res.json({
success: true,
data: screenSubTables,
globalMainTables: globalMainTables, // 메인 테이블로 분류되어야 하는 테이블 목록
});
} catch (error: any) {
logger.error("화면 서브 테이블 정보 조회 실패:", error);

View File

@@ -731,6 +731,14 @@ export class ScreenManagementService {
WHERE screen_id = $1 AND is_active = 'Y'`,
[screenId],
);
// 5. 화면 그룹 연결 삭제 (screen_group_screens)
await client.query(
`DELETE FROM screen_group_screens WHERE screen_id = $1`,
[screenId],
);
logger.info("화면 삭제 시 그룹 연결 해제", { screenId });
});
}
@@ -5110,18 +5118,6 @@ export class ScreenManagementService {
console.log(
`V2 레이아웃 로드 완료: ${layout.layout_data?.components?.length || 0}개 컴포넌트`,
);
// 🐛 디버깅: finished_timeline의 fieldMapping 확인
const splitPanel = layout.layout_data?.components?.find((c: any) =>
c.url?.includes("v2-split-panel-layout")
);
const finishedTimeline = splitPanel?.overrides?.rightPanel?.components?.find(
(c: any) => c.id === "finished_timeline"
);
if (finishedTimeline) {
console.log("🐛 [Backend] finished_timeline fieldMapping:", JSON.stringify(finishedTimeline.componentConfig?.fieldMapping));
}
return layout.layout_data;
}
@@ -5161,20 +5157,16 @@ export class ScreenManagementService {
...layoutData
};
// SUPER_ADMIN인 경우 화면 정의의 company_code로 저장 (로드와 일관성 유지)
const saveCompanyCode = companyCode === "*" ? existingScreen.company_code : companyCode;
console.log(`저장할 company_code: ${saveCompanyCode} (원본: ${companyCode}, 화면 정의: ${existingScreen.company_code})`);
// UPSERT (있으면 업데이트, 없으면 삽입)
await query(
`INSERT INTO screen_layouts_v2 (screen_id, company_code, layout_data, created_at, updated_at)
VALUES ($1, $2, $3, NOW(), NOW())
ON CONFLICT (screen_id, company_code)
DO UPDATE SET layout_data = $3, updated_at = NOW()`,
[screenId, saveCompanyCode, JSON.stringify(dataToSave)],
[screenId, companyCode, JSON.stringify(dataToSave)],
);
console.log(`V2 레이아웃 저장 완료 (company_code: ${saveCompanyCode})`);
console.log(`V2 레이아웃 저장 완료`);
}
}