feat: 멀티테넌시 지원을 위한 레이어 관리 기능 추가
- 레이어 목록 조회, 특정 레이어 레이아웃 조회, 레이어 삭제 및 조건 설정 업데이트 기능을 추가했습니다. - 엔티티 참조 데이터 조회 및 공통 코드 데이터 조회에 멀티테넌시 필터를 적용하여 인증된 사용자의 회사 코드에 따라 데이터 접근을 제한했습니다. - 레이어 관리 패널에서 기본 레이어와 조건부 레이어의 컴포넌트를 통합하여 조건부 영역의 표시를 개선했습니다. - 레이아웃 저장 시 레이어 ID를 포함하여 레이어별로 저장할 수 있도록 변경했습니다.
This commit is contained in:
@@ -28,6 +28,7 @@ interface LegacyComponentData {
|
||||
|
||||
interface LegacyLayoutData {
|
||||
components: LegacyComponentData[];
|
||||
layers?: any[]; // 레이어 시스템
|
||||
gridSettings?: any;
|
||||
screenResolution?: any;
|
||||
metadata?: any;
|
||||
@@ -140,21 +141,22 @@ function applyDefaultsToSplitPanelComponents(mergedConfig: Record<string, any>):
|
||||
// V2 → Legacy 변환 (로드 시)
|
||||
// ============================================
|
||||
export function convertV2ToLegacy(v2Layout: LayoutV2 | null): LegacyLayoutData | null {
|
||||
if (!v2Layout || !v2Layout.components) {
|
||||
if (!v2Layout) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const components: LegacyComponentData[] = v2Layout.components.map((comp) => {
|
||||
// V2 컴포넌트를 Legacy 컴포넌트로 변환하는 함수 (레이어 내 컴포넌트에도 재사용)
|
||||
const convertV2Component = (comp: ComponentV2, layerId?: string): LegacyComponentData => {
|
||||
const componentType = getComponentTypeFromUrl(comp.url);
|
||||
const defaults = getDefaultsByUrl(comp.url);
|
||||
let mergedConfig = mergeComponentConfig(defaults, comp.overrides);
|
||||
|
||||
// 🆕 분할 패널인 경우 내부 컴포넌트에도 기본값 적용
|
||||
// 분할 패널인 경우 내부 컴포넌트에도 기본값 적용
|
||||
if (componentType === "v2-split-panel-layout") {
|
||||
mergedConfig = applyDefaultsToSplitPanelComponents(mergedConfig);
|
||||
}
|
||||
|
||||
// 🆕 탭 위젯인 경우 탭 내부 컴포넌트에도 기본값 적용
|
||||
// 탭 위젯인 경우 탭 내부 컴포넌트에도 기본값 적용
|
||||
if (componentType === "v2-tabs-widget" && mergedConfig.tabs) {
|
||||
mergedConfig = {
|
||||
...mergedConfig,
|
||||
@@ -170,7 +172,6 @@ export function convertV2ToLegacy(v2Layout: LayoutV2 | null): LegacyLayoutData |
|
||||
};
|
||||
}
|
||||
|
||||
// 🆕 overrides에서 상위 레벨 속성들 추출
|
||||
const overrides = comp.overrides || {};
|
||||
|
||||
return {
|
||||
@@ -181,45 +182,68 @@ export function convertV2ToLegacy(v2Layout: LayoutV2 | null): LegacyLayoutData |
|
||||
position: comp.position,
|
||||
size: comp.size,
|
||||
componentConfig: mergedConfig,
|
||||
// 🆕 상위 레벨 속성 복원 (테이블/컬럼 연결 정보)
|
||||
// 상위 레벨 속성 복원
|
||||
tableName: overrides.tableName,
|
||||
columnName: overrides.columnName,
|
||||
label: overrides.label || mergedConfig.label || "", // 라벨이 없으면 빈 문자열
|
||||
label: overrides.label || mergedConfig.label || "",
|
||||
required: overrides.required,
|
||||
readonly: overrides.readonly,
|
||||
hidden: overrides.hidden, // 🆕 숨김 설정 복원
|
||||
hidden: overrides.hidden,
|
||||
codeCategory: overrides.codeCategory,
|
||||
inputType: overrides.inputType,
|
||||
webType: overrides.webType,
|
||||
// 🆕 autoFill 설정 복원 (자동 입력 기능)
|
||||
autoFill: overrides.autoFill,
|
||||
// 🆕 style 설정 복원 (라벨 텍스트, 라벨 스타일 등)
|
||||
style: overrides.style || {},
|
||||
// 🔧 webTypeConfig 복원 (버튼 제어기능, 플로우 가시성 등)
|
||||
webTypeConfig: overrides.webTypeConfig || {},
|
||||
// 기존 구조 호환을 위한 추가 필드
|
||||
parentId: null,
|
||||
gridColumns: 12,
|
||||
gridRowIndex: 0,
|
||||
// 🆕 레이어 ID 복원
|
||||
...(layerId ? { layerId } : {}),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// 🆕 레이어 구조가 있는 경우 (v2.1)
|
||||
const v2Layers = (v2Layout as any).layers;
|
||||
if (v2Layers && Array.isArray(v2Layers) && v2Layers.length > 0) {
|
||||
// 모든 레이어의 컴포넌트를 평탄화하여 layout.components에 저장 (layerId 포함)
|
||||
const allComponents: LegacyComponentData[] = [];
|
||||
const legacyLayers = v2Layers.map((layer: any) => {
|
||||
const layerComponents = (layer.components || []).map((comp: any) =>
|
||||
convertV2Component(comp, layer.id)
|
||||
);
|
||||
allComponents.push(...layerComponents);
|
||||
|
||||
return {
|
||||
...layer,
|
||||
// layer.components는 legacy 변환 시 빈 배열로 (layout.components + layerId 방식 사용)
|
||||
components: [],
|
||||
};
|
||||
});
|
||||
|
||||
return {
|
||||
components: allComponents,
|
||||
layers: legacyLayers,
|
||||
gridSettings: v2Layout.gridSettings || {
|
||||
enabled: true, size: 20, color: "#d1d5db", opacity: 0.5,
|
||||
snapToGrid: true, columns: 12, gap: 16, padding: 16,
|
||||
},
|
||||
screenResolution: v2Layout.screenResolution || { width: 1920, height: 1080 },
|
||||
};
|
||||
}
|
||||
|
||||
// 레이어 없는 기존 방식
|
||||
if (!v2Layout.components) return null;
|
||||
|
||||
const components: LegacyComponentData[] = v2Layout.components.map((comp) => convertV2Component(comp));
|
||||
|
||||
return {
|
||||
components,
|
||||
gridSettings: v2Layout.gridSettings || {
|
||||
enabled: true,
|
||||
size: 20,
|
||||
color: "#d1d5db",
|
||||
opacity: 0.5,
|
||||
snapToGrid: true,
|
||||
columns: 12,
|
||||
gap: 16,
|
||||
padding: 16,
|
||||
},
|
||||
screenResolution: v2Layout.screenResolution || {
|
||||
width: 1920,
|
||||
height: 1080,
|
||||
enabled: true, size: 20, color: "#d1d5db", opacity: 0.5,
|
||||
snapToGrid: true, columns: 12, gap: 16, padding: 16,
|
||||
},
|
||||
screenResolution: v2Layout.screenResolution || { width: 1920, height: 1080 },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -227,7 +251,8 @@ export function convertV2ToLegacy(v2Layout: LayoutV2 | null): LegacyLayoutData |
|
||||
// Legacy → V2 변환 (저장 시)
|
||||
// ============================================
|
||||
export function convertLegacyToV2(legacyLayout: LegacyLayoutData): LayoutV2 {
|
||||
const components: ComponentV2[] = legacyLayout.components.map((comp, index) => {
|
||||
// 컴포넌트 변환 함수 (레이어 내 컴포넌트 변환에도 재사용)
|
||||
const convertComponent = (comp: LegacyComponentData, index: number): ComponentV2 => {
|
||||
// 컴포넌트 타입 결정
|
||||
const componentType = comp.componentType || comp.widgetType || comp.type || "unknown";
|
||||
const url = getComponentUrl(componentType);
|
||||
@@ -301,12 +326,33 @@ export function convertLegacyToV2(legacyLayout: LegacyLayoutData): LayoutV2 {
|
||||
displayOrder: index,
|
||||
overrides: overrides,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// 🆕 레이어 정보 변환 (layers가 있으면 레이어 구조로 저장)
|
||||
const legacyLayers = (legacyLayout as any).layers;
|
||||
if (legacyLayers && Array.isArray(legacyLayers) && legacyLayers.length > 0) {
|
||||
const v2Layers = legacyLayers.map((layer: any) => ({
|
||||
...layer,
|
||||
// 레이어 내 컴포넌트를 V2 형식으로 변환
|
||||
components: (layer.components || []).map((comp: any, idx: number) => convertComponent(comp, idx)),
|
||||
}));
|
||||
|
||||
return {
|
||||
version: "2.1",
|
||||
layers: v2Layers,
|
||||
components: [], // 레이어 구조 사용 시 상위 components는 빈 배열
|
||||
gridSettings: legacyLayout.gridSettings,
|
||||
screenResolution: legacyLayout.screenResolution,
|
||||
metadata: legacyLayout.metadata,
|
||||
};
|
||||
}
|
||||
|
||||
// 레이어 없으면 기존 방식 (컴포넌트만)
|
||||
const components = legacyLayout.components.map((comp, index) => convertComponent(comp, index));
|
||||
|
||||
return {
|
||||
version: "2.0",
|
||||
components,
|
||||
// 레이아웃 메타데이터 포함
|
||||
gridSettings: legacyLayout.gridSettings,
|
||||
screenResolution: legacyLayout.screenResolution,
|
||||
metadata: legacyLayout.metadata,
|
||||
@@ -317,7 +363,11 @@ export function convertLegacyToV2(legacyLayout: LegacyLayoutData): LayoutV2 {
|
||||
// V2 레이아웃 유효성 검사
|
||||
// ============================================
|
||||
export function isValidV2Layout(data: any): data is LayoutV2 {
|
||||
return data && typeof data === "object" && data.version === "2.0" && Array.isArray(data.components);
|
||||
if (!data || typeof data !== "object") return false;
|
||||
// v2.0: components 기반, v2.1: layers 기반
|
||||
const isV2 = data.version === "2.0" && Array.isArray(data.components);
|
||||
const isV21 = data.version === "2.1" && Array.isArray(data.layers);
|
||||
return isV2 || isV21;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
|
||||
Reference in New Issue
Block a user