화면 복사기능 수정

This commit is contained in:
kjs
2025-11-19 10:03:38 +09:00
parent 5f026e88ab
commit b74cb94191
6 changed files with 887 additions and 180 deletions

View File

@@ -2101,55 +2101,109 @@ export class ScreenManagementService {
}
/**
* 화면에 연결된 모달 화면들을 자동 감지
* 버튼 컴포넌트 popup 액션에서 targetScreenId를 추출
* 화면에 연결된 모달/화면들을 재귀적으로 자동 감지
* - 버튼 컴포넌트: popup/modal/edit/openModalWithData 액션 targetScreenId
* - 조건부 컨테이너: sections[].screenId (조건별 화면 할당)
* - 중첩된 화면들도 모두 감지 (재귀)
*/
async detectLinkedModalScreens(
screenId: number
): Promise<{ screenId: number; screenName: string; screenCode: string }[]> {
// 화면의 모든 레이아웃 조회
const layouts = await query<any>(
`SELECT layout_id, properties
FROM screen_layouts
WHERE screen_id = $1
AND component_type = 'component'
AND properties IS NOT NULL`,
[screenId]
);
console.log(`\n🔍 [재귀 감지 시작] 화면 ID: ${screenId}`);
const allLinkedScreenIds = new Set<number>();
const visited = new Set<number>(); // 무한 루프 방지
const queue: number[] = [screenId]; // BFS 큐
const linkedScreenIds = new Set<number>();
// BFS로 연결된 모든 화면 탐색
while (queue.length > 0) {
const currentScreenId = queue.shift()!;
// 이미 방문한 화면은 스킵 (순환 참조 방지)
if (visited.has(currentScreenId)) {
console.log(`⏭️ 이미 방문한 화면 스킵: ${currentScreenId}`);
continue;
}
visited.add(currentScreenId);
console.log(`\n📋 현재 탐색 중인 화면: ${currentScreenId} (깊이: ${visited.size})`);
// 각 레이아웃에서 버튼의 popup/modal/edit 액션 확인
for (const layout of layouts) {
try {
const properties = layout.properties;
// 버튼 컴포넌트인지 확인
if (properties?.componentType === "button" || properties?.componentType?.startsWith("button-")) {
const action = properties?.componentConfig?.action;
// 현재 화면의 모든 레이아웃 조회
const layouts = await query<any>(
`SELECT layout_id, properties
FROM screen_layouts
WHERE screen_id = $1
AND component_type = 'component'
AND properties IS NOT NULL`,
[currentScreenId]
);
console.log(` 📦 레이아웃 개수: ${layouts.length}`);
// 각 레이아웃에서 연결된 화면 ID 확인
for (const layout of layouts) {
try {
const properties = layout.properties;
// popup, modal, edit 액션이고 targetScreenId가 있는 경우
// edit 액션도 수정 폼 모달을 열기 때문에 포함
if ((action?.type === "popup" || action?.type === "modal" || action?.type === "edit") && action?.targetScreenId) {
const targetScreenId = parseInt(action.targetScreenId);
if (!isNaN(targetScreenId)) {
linkedScreenIds.add(targetScreenId);
console.log(`🔗 연결된 모달 화면 발견: screenId=${targetScreenId}, actionType=${action.type} (레이아웃 ${layout.layout_id})`);
// 1. 버튼 컴포넌트의 액션 확인
if (properties?.componentType === "button" || properties?.componentType?.startsWith("button-")) {
const action = properties?.componentConfig?.action;
const modalActionTypes = ["popup", "modal", "edit", "openModalWithData"];
if (modalActionTypes.includes(action?.type) && action?.targetScreenId) {
const targetScreenId = parseInt(action.targetScreenId);
if (!isNaN(targetScreenId) && targetScreenId !== currentScreenId) {
// 메인 화면이 아닌 경우에만 추가
if (targetScreenId !== screenId) {
allLinkedScreenIds.add(targetScreenId);
}
// 아직 방문하지 않은 화면이면 큐에 추가
if (!visited.has(targetScreenId)) {
queue.push(targetScreenId);
console.log(` 🔗 [버튼] 연결된 화면 발견: ${targetScreenId} (action: ${action.type}) → 큐에 추가`);
}
}
}
}
// 2. conditional-container 컴포넌트의 sections 확인
if (properties?.componentType === "conditional-container") {
const sections = properties?.componentConfig?.sections || [];
for (const section of sections) {
if (section?.screenId) {
const sectionScreenId = parseInt(section.screenId);
if (!isNaN(sectionScreenId) && sectionScreenId !== currentScreenId) {
// 메인 화면이 아닌 경우에만 추가
if (sectionScreenId !== screenId) {
allLinkedScreenIds.add(sectionScreenId);
}
// 아직 방문하지 않은 화면이면 큐에 추가
if (!visited.has(sectionScreenId)) {
queue.push(sectionScreenId);
console.log(` 🔗 [조건부컨테이너] 연결된 화면 발견: ${sectionScreenId} (condition: ${section.condition}) → 큐에 추가`);
}
}
}
}
}
} catch (error) {
console.warn(` ⚠️ 레이아웃 ${layout.layout_id} 파싱 오류:`, error);
}
} catch (error) {
// JSON 파싱 오류 등은 무시하고 계속 진행
console.warn(`레이아웃 ${layout.layout_id} 파싱 오류:`, error);
}
}
console.log(`\n✅ [재귀 감지 완료] 총 방문한 화면: ${visited.size}개, 연결된 화면: ${allLinkedScreenIds.size}`);
console.log(` 방문한 화면 ID: [${Array.from(visited).join(", ")}]`);
console.log(` 연결된 화면 ID: [${Array.from(allLinkedScreenIds).join(", ")}]`);
// 감지된 화면 ID들의 정보 조회
if (linkedScreenIds.size === 0) {
if (allLinkedScreenIds.size === 0) {
console.log(` 연결된 화면이 없습니다.`);
return [];
}
const screenIds = Array.from(linkedScreenIds);
const screenIds = Array.from(allLinkedScreenIds);
const placeholders = screenIds.map((_, i) => `$${i + 1}`).join(", ");
const linkedScreens = await query<any>(
@@ -2161,6 +2215,11 @@ export class ScreenManagementService {
screenIds
);
console.log(`\n📋 최종 감지된 화면 목록:`);
linkedScreens.forEach((s: any) => {
console.log(` - ${s.screen_name} (ID: ${s.screen_id}, 코드: ${s.screen_code})`);
});
return linkedScreens.map((s) => ({
screenId: s.screen_id,
screenName: s.screen_name,
@@ -2430,23 +2489,23 @@ export class ScreenManagementService {
for (const layout of layouts) {
try {
const properties = layout.properties;
let needsUpdate = false;
// 버튼 컴포넌트인지 확인
// 1. 버튼 컴포넌트의 targetScreenId 업데이트
if (
properties?.componentType === "button" ||
properties?.componentType?.startsWith("button-")
) {
const action = properties?.componentConfig?.action;
// targetScreenId가 있는 액션 (popup, modal, edit)
// targetScreenId가 있는 액션 (popup, modal, edit, openModalWithData)
const modalActionTypes = ["popup", "modal", "edit", "openModalWithData"];
if (
(action?.type === "popup" ||
action?.type === "modal" ||
action?.type === "edit") &&
modalActionTypes.includes(action?.type) &&
action?.targetScreenId
) {
const oldScreenId = parseInt(action.targetScreenId);
console.log(`🔍 버튼 발견: layout ${layout.layout_id}, action=${action.type}, targetScreenId=${oldScreenId}`);
console.log(`🔍 [버튼] 발견: layout ${layout.layout_id}, action=${action.type}, targetScreenId=${oldScreenId}`);
// 매핑에 있으면 업데이트
if (screenIdMapping.has(oldScreenId)) {
@@ -2456,31 +2515,63 @@ export class ScreenManagementService {
// properties 업데이트
properties.componentConfig.action.targetScreenId =
newScreenId.toString();
needsUpdate = true;
// 데이터베이스 업데이트
await query(
`UPDATE screen_layouts
SET properties = $1
WHERE layout_id = $2`,
[JSON.stringify(properties), layout.layout_id]
);
updateCount++;
console.log(
`🔗 버튼 targetScreenId 업데이트: ${oldScreenId}${newScreenId} (layout ${layout.layout_id})`
`🔗 [버튼] targetScreenId 업데이트 준비: ${oldScreenId}${newScreenId} (layout ${layout.layout_id})`
);
} else {
console.log(`⚠️ 매핑 없음: ${oldScreenId} (업데이트 건너뜀)`);
}
}
}
// 2. conditional-container 컴포넌트의 sections[].screenId 업데이트
if (properties?.componentType === "conditional-container") {
const sections = properties?.componentConfig?.sections || [];
for (const section of sections) {
if (section?.screenId) {
const oldScreenId = parseInt(section.screenId);
console.log(`🔍 [조건부컨테이너] section 발견: layout ${layout.layout_id}, condition=${section.condition}, screenId=${oldScreenId}`);
// 매핑에 있으면 업데이트
if (screenIdMapping.has(oldScreenId)) {
const newScreenId = screenIdMapping.get(oldScreenId)!;
console.log(`✅ 매핑 발견: ${oldScreenId}${newScreenId}`);
// section.screenId 업데이트
section.screenId = newScreenId;
needsUpdate = true;
console.log(
`🔗 [조건부컨테이너] screenId 업데이트 준비: ${oldScreenId}${newScreenId} (layout ${layout.layout_id}, condition=${section.condition})`
);
} else {
console.log(`⚠️ 매핑 없음: ${oldScreenId} (업데이트 건너뜀)`);
}
}
}
}
// 3. 업데이트가 필요한 경우 DB 저장
if (needsUpdate) {
await query(
`UPDATE screen_layouts
SET properties = $1
WHERE layout_id = $2`,
[JSON.stringify(properties), layout.layout_id]
);
updateCount++;
console.log(`💾 레이아웃 ${layout.layout_id} 업데이트 완료`);
}
} catch (error) {
console.warn(`❌ 레이아웃 ${layout.layout_id} 업데이트 오류:`, error);
// 개별 레이아웃 오류는 무시하고 계속 진행
}
}
console.log(`✅ 총 ${updateCount}버튼의 targetScreenId 업데이트 완료`);
console.log(`✅ 총 ${updateCount}레이아웃의 연결된 화면 ID 업데이트 완료 (버튼 + 조건부컨테이너)`);
return updateCount;
}
}