feat: V2 레이아웃 처리 개선 및 새로운 V2 레이아웃 데이터 구조 도입
- 기존 레이아웃 처리 로직을 V2 레이아웃에 맞게 수정하였습니다. - V2 레이아웃에서 layout_data를 조회하고, 변경 여부를 확인하는 로직을 추가하였습니다. - 레이아웃 데이터의 참조 ID 업데이트 및 flowId, numberingRuleId 수집 기능을 구현하였습니다. - V2Media 컴포넌트를 통합하여 미디어 관련 기능을 강화하였습니다. - 레이아웃 처리 시 V2 레이아웃의 컴포넌트 매핑 및 데이터 복사를 효율적으로 처리하도록 개선하였습니다.
This commit is contained in:
@@ -1556,22 +1556,22 @@ export class MenuCopyService {
|
||||
// === 기존 복사본이 있는 경우: 업데이트 ===
|
||||
const existingScreenId = existingCopy.screen_id;
|
||||
|
||||
// 원본 레이아웃 조회
|
||||
const sourceLayoutsResult = await client.query<ScreenLayout>(
|
||||
`SELECT * FROM screen_layouts WHERE screen_id = $1 ORDER BY display_order`,
|
||||
// 원본 V2 레이아웃 조회
|
||||
const sourceLayoutV2Result = await client.query<{ layout_data: any }>(
|
||||
`SELECT layout_data FROM screen_layouts_v2 WHERE screen_id = $1`,
|
||||
[originalScreenId]
|
||||
);
|
||||
|
||||
// 대상 레이아웃 조회
|
||||
const targetLayoutsResult = await client.query<ScreenLayout>(
|
||||
`SELECT * FROM screen_layouts WHERE screen_id = $1 ORDER BY display_order`,
|
||||
// 대상 V2 레이아웃 조회
|
||||
const targetLayoutV2Result = await client.query<{ layout_data: any }>(
|
||||
`SELECT layout_data FROM screen_layouts_v2 WHERE screen_id = $1`,
|
||||
[existingScreenId]
|
||||
);
|
||||
|
||||
// 변경 여부 확인 (레이아웃 개수 또는 내용 비교)
|
||||
const hasChanges = this.hasLayoutChanges(
|
||||
sourceLayoutsResult.rows,
|
||||
targetLayoutsResult.rows
|
||||
// 변경 여부 확인 (V2 레이아웃 비교)
|
||||
const hasChanges = this.hasLayoutChangesV2(
|
||||
sourceLayoutV2Result.rows[0]?.layout_data,
|
||||
targetLayoutV2Result.rows[0]?.layout_data
|
||||
);
|
||||
|
||||
if (hasChanges) {
|
||||
@@ -1673,9 +1673,9 @@ export class MenuCopyService {
|
||||
}
|
||||
}
|
||||
|
||||
// === 2단계: screen_layouts 처리 (이제 screenIdMap이 완성됨) ===
|
||||
// === 2단계: screen_layouts_v2 처리 (이제 screenIdMap이 완성됨) ===
|
||||
logger.info(
|
||||
`\n📐 레이아웃 처리 시작 (screenIdMap 완성: ${screenIdMap.size}개)`
|
||||
`\n📐 V2 레이아웃 처리 시작 (screenIdMap 완성: ${screenIdMap.size}개)`
|
||||
);
|
||||
|
||||
for (const {
|
||||
@@ -1685,91 +1685,51 @@ export class MenuCopyService {
|
||||
isUpdate,
|
||||
} of screenDefsToProcess) {
|
||||
try {
|
||||
// 원본 레이아웃 조회
|
||||
const layoutsResult = await client.query<ScreenLayout>(
|
||||
`SELECT * FROM screen_layouts WHERE screen_id = $1 ORDER BY display_order`,
|
||||
// 원본 V2 레이아웃 조회
|
||||
const layoutV2Result = await client.query<{ layout_data: any }>(
|
||||
`SELECT layout_data FROM screen_layouts_v2 WHERE screen_id = $1`,
|
||||
[originalScreenId]
|
||||
);
|
||||
|
||||
if (isUpdate) {
|
||||
// 업데이트: 기존 레이아웃 삭제 후 새로 삽입
|
||||
await client.query(
|
||||
`DELETE FROM screen_layouts WHERE screen_id = $1`,
|
||||
[targetScreenId]
|
||||
const layoutData = layoutV2Result.rows[0]?.layout_data;
|
||||
const components = layoutData?.components || [];
|
||||
|
||||
if (layoutData && components.length > 0) {
|
||||
// component_id 매핑 생성 (원본 → 새 ID)
|
||||
const componentIdMap = new Map<string, string>();
|
||||
const timestamp = Date.now();
|
||||
components.forEach((comp: any, idx: number) => {
|
||||
const newComponentId = `comp_${timestamp}_${idx}_${Math.random().toString(36).substr(2, 5)}`;
|
||||
componentIdMap.set(comp.id, newComponentId);
|
||||
});
|
||||
|
||||
// V2 레이아웃 데이터 복사 및 참조 업데이트
|
||||
const updatedLayoutData = this.updateReferencesInLayoutDataV2(
|
||||
layoutData,
|
||||
componentIdMap,
|
||||
screenIdMap,
|
||||
flowIdMap,
|
||||
numberingRuleIdMap,
|
||||
menuIdMap
|
||||
);
|
||||
logger.info(` ↳ 기존 레이아웃 삭제 (업데이트 준비)`);
|
||||
}
|
||||
|
||||
// component_id 매핑 생성 (원본 → 새 ID)
|
||||
const componentIdMap = new Map<string, string>();
|
||||
const timestamp = Date.now();
|
||||
layoutsResult.rows.forEach((layout, idx) => {
|
||||
const newComponentId = `comp_${timestamp}_${idx}_${Math.random().toString(36).substr(2, 5)}`;
|
||||
componentIdMap.set(layout.component_id, newComponentId);
|
||||
});
|
||||
|
||||
// 레이아웃 배치 삽입 준비
|
||||
if (layoutsResult.rows.length > 0) {
|
||||
const layoutValues: string[] = [];
|
||||
const layoutParams: any[] = [];
|
||||
let paramIdx = 1;
|
||||
|
||||
for (const layout of layoutsResult.rows) {
|
||||
const newComponentId = componentIdMap.get(layout.component_id)!;
|
||||
|
||||
const newParentId = layout.parent_id
|
||||
? componentIdMap.get(layout.parent_id) || layout.parent_id
|
||||
: null;
|
||||
const newZoneId = layout.zone_id
|
||||
? componentIdMap.get(layout.zone_id) || layout.zone_id
|
||||
: null;
|
||||
|
||||
const updatedProperties = this.updateReferencesInProperties(
|
||||
layout.properties,
|
||||
screenIdMap,
|
||||
flowIdMap,
|
||||
numberingRuleIdMap,
|
||||
menuIdMap
|
||||
);
|
||||
|
||||
layoutValues.push(
|
||||
`($${paramIdx}, $${paramIdx + 1}, $${paramIdx + 2}, $${paramIdx + 3}, $${paramIdx + 4}, $${paramIdx + 5}, $${paramIdx + 6}, $${paramIdx + 7}, $${paramIdx + 8}, $${paramIdx + 9}, $${paramIdx + 10}, $${paramIdx + 11}, $${paramIdx + 12}, $${paramIdx + 13})`
|
||||
);
|
||||
layoutParams.push(
|
||||
targetScreenId,
|
||||
layout.component_type,
|
||||
newComponentId,
|
||||
newParentId,
|
||||
layout.position_x,
|
||||
layout.position_y,
|
||||
layout.width,
|
||||
layout.height,
|
||||
updatedProperties,
|
||||
layout.display_order,
|
||||
layout.layout_type,
|
||||
layout.layout_config,
|
||||
layout.zones_config,
|
||||
newZoneId
|
||||
);
|
||||
paramIdx += 14;
|
||||
}
|
||||
|
||||
// 배치 INSERT
|
||||
// V2 레이아웃 저장 (UPSERT)
|
||||
await client.query(
|
||||
`INSERT INTO screen_layouts (
|
||||
screen_id, component_type, component_id, parent_id,
|
||||
position_x, position_y, width, height, properties,
|
||||
display_order, layout_type, layout_config, zones_config, zone_id
|
||||
) VALUES ${layoutValues.join(", ")}`,
|
||||
layoutParams
|
||||
`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()`,
|
||||
[targetScreenId, targetCompanyCode, JSON.stringify(updatedLayoutData)]
|
||||
);
|
||||
}
|
||||
|
||||
const action = isUpdate ? "업데이트" : "복사";
|
||||
logger.info(` ↳ 레이아웃 ${action}: ${layoutsResult.rows.length}개`);
|
||||
const action = isUpdate ? "업데이트" : "복사";
|
||||
logger.info(` ↳ V2 레이아웃 ${action}: ${components.length}개 컴포넌트`);
|
||||
} else {
|
||||
logger.info(` ↳ V2 레이아웃 없음 (스킵): screen_id=${originalScreenId}`);
|
||||
}
|
||||
} catch (error: any) {
|
||||
logger.error(
|
||||
`❌ 레이아웃 처리 실패: screen_id=${originalScreenId}`,
|
||||
`❌ V2 레이아웃 처리 실패: screen_id=${originalScreenId}`,
|
||||
error
|
||||
);
|
||||
throw error;
|
||||
@@ -1835,6 +1795,83 @@ export class MenuCopyService {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 레이아웃 변경 여부 확인 (screen_layouts_v2용)
|
||||
*/
|
||||
private hasLayoutChangesV2(
|
||||
sourceLayoutData: any,
|
||||
targetLayoutData: any
|
||||
): boolean {
|
||||
// 1. 둘 다 없으면 변경 없음
|
||||
if (!sourceLayoutData && !targetLayoutData) return false;
|
||||
|
||||
// 2. 하나만 있으면 변경됨
|
||||
if (!sourceLayoutData || !targetLayoutData) return true;
|
||||
|
||||
// 3. components 배열 비교
|
||||
const sourceComps = sourceLayoutData.components || [];
|
||||
const targetComps = targetLayoutData.components || [];
|
||||
|
||||
if (sourceComps.length !== targetComps.length) return true;
|
||||
|
||||
// 4. 각 컴포넌트 비교 (url, position, size, overrides)
|
||||
for (let i = 0; i < sourceComps.length; i++) {
|
||||
const s = sourceComps[i];
|
||||
const t = targetComps[i];
|
||||
|
||||
if (s.url !== t.url) return true;
|
||||
if (JSON.stringify(s.position) !== JSON.stringify(t.position)) return true;
|
||||
if (JSON.stringify(s.size) !== JSON.stringify(t.size)) return true;
|
||||
if (JSON.stringify(s.overrides) !== JSON.stringify(t.overrides)) return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* V2 레이아웃 데이터의 참조 ID들을 업데이트 (componentId, flowId, ruleId, screenId, menuId)
|
||||
*/
|
||||
private updateReferencesInLayoutDataV2(
|
||||
layoutData: any,
|
||||
componentIdMap: Map<string, string>,
|
||||
screenIdMap: Map<number, number>,
|
||||
flowIdMap: Map<number, number>,
|
||||
numberingRuleIdMap?: Map<string, string>,
|
||||
menuIdMap?: Map<number, number>
|
||||
): any {
|
||||
if (!layoutData?.components) return layoutData;
|
||||
|
||||
const updatedComponents = layoutData.components.map((comp: any) => {
|
||||
// 1. componentId 매핑
|
||||
const newId = componentIdMap.get(comp.id) || comp.id;
|
||||
|
||||
// 2. overrides 복사 및 재귀적 참조 업데이트
|
||||
let overrides = JSON.parse(JSON.stringify(comp.overrides || {}));
|
||||
|
||||
// 재귀적으로 모든 참조 업데이트
|
||||
this.recursiveUpdateReferences(
|
||||
overrides,
|
||||
screenIdMap,
|
||||
flowIdMap,
|
||||
"",
|
||||
numberingRuleIdMap,
|
||||
menuIdMap
|
||||
);
|
||||
|
||||
return {
|
||||
...comp,
|
||||
id: newId,
|
||||
overrides,
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
...layoutData,
|
||||
components: updatedComponents,
|
||||
updatedAt: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 메뉴 위상 정렬 (부모 먼저)
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user