fix: 화면 복제 기능 개선 및 관련 버그 수정
- 화면 복제 기능을 개선하여 DB 구조 개편 후의 효율적인 화면 관리를 지원합니다. - 그룹 복제 시 버튼의 `targetScreenId`가 새 화면으로 매핑되지 않는 버그를 수정하였습니다. - 관련된 서비스 및 쿼리에서 `table_type_columns`를 사용하여 라벨 정보를 조회하도록 변경하였습니다. - 여러 컨트롤러 및 서비스에서 `column_labels` 대신 `table_type_columns`를 참조하도록 업데이트하였습니다.
This commit is contained in:
263
backend-node/src/utils/componentDefaults.ts
Normal file
263
backend-node/src/utils/componentDefaults.ts
Normal file
@@ -0,0 +1,263 @@
|
||||
/**
|
||||
* 컴포넌트 기본값 및 복원 유틸리티
|
||||
*
|
||||
* screen_layouts_v2 테이블의 config_overrides를 기본값과 병합하여
|
||||
* 전체 componentConfig를 복원합니다.
|
||||
*/
|
||||
|
||||
// 컴포넌트별 기본값 맵
|
||||
export const componentDefaults: Record<string, any> = {
|
||||
"button-primary": {
|
||||
type: "button-primary",
|
||||
text: "저장",
|
||||
actionType: "button",
|
||||
variant: "primary",
|
||||
webType: "button",
|
||||
},
|
||||
"v2-button-primary": {
|
||||
type: "v2-button-primary",
|
||||
text: "저장",
|
||||
actionType: "button",
|
||||
variant: "primary",
|
||||
webType: "button",
|
||||
},
|
||||
"text-input": {
|
||||
type: "text-input",
|
||||
webType: "text",
|
||||
format: "none",
|
||||
multiline: false,
|
||||
placeholder: "텍스트를 입력하세요",
|
||||
},
|
||||
"number-input": {
|
||||
type: "number-input",
|
||||
webType: "number",
|
||||
placeholder: "숫자를 입력하세요",
|
||||
},
|
||||
"date-input": {
|
||||
type: "date-input",
|
||||
webType: "date",
|
||||
format: "YYYY-MM-DD",
|
||||
showTime: false,
|
||||
placeholder: "날짜를 선택하세요",
|
||||
},
|
||||
"select-basic": {
|
||||
type: "select-basic",
|
||||
webType: "code",
|
||||
placeholder: "선택하세요",
|
||||
options: [],
|
||||
},
|
||||
"file-upload": {
|
||||
type: "file-upload",
|
||||
webType: "file",
|
||||
placeholder: "입력하세요",
|
||||
},
|
||||
"table-list": {
|
||||
type: "table-list",
|
||||
webType: "table",
|
||||
displayMode: "table",
|
||||
showHeader: true,
|
||||
showFooter: true,
|
||||
autoLoad: true,
|
||||
autoWidth: true,
|
||||
stickyHeader: false,
|
||||
height: "auto",
|
||||
columns: [],
|
||||
pagination: {
|
||||
enabled: true,
|
||||
pageSize: 20,
|
||||
showSizeSelector: true,
|
||||
showPageInfo: true,
|
||||
pageSizeOptions: [10, 20, 50, 100],
|
||||
},
|
||||
checkbox: {
|
||||
enabled: true,
|
||||
multiple: true,
|
||||
position: "left",
|
||||
selectAll: true,
|
||||
},
|
||||
horizontalScroll: {
|
||||
enabled: false,
|
||||
},
|
||||
filter: {
|
||||
enabled: false,
|
||||
filters: [],
|
||||
},
|
||||
actions: {
|
||||
showActions: false,
|
||||
actions: [],
|
||||
bulkActions: false,
|
||||
bulkActionList: [],
|
||||
},
|
||||
tableStyle: {
|
||||
theme: "default",
|
||||
headerStyle: "default",
|
||||
rowHeight: "normal",
|
||||
alternateRows: false,
|
||||
hoverEffect: true,
|
||||
borderStyle: "light",
|
||||
},
|
||||
},
|
||||
"v2-table-list": {
|
||||
type: "v2-table-list",
|
||||
webType: "table",
|
||||
displayMode: "table",
|
||||
showHeader: true,
|
||||
showFooter: true,
|
||||
autoLoad: true,
|
||||
autoWidth: true,
|
||||
stickyHeader: false,
|
||||
height: "auto",
|
||||
columns: [],
|
||||
pagination: {
|
||||
enabled: true,
|
||||
pageSize: 20,
|
||||
showSizeSelector: true,
|
||||
showPageInfo: true,
|
||||
pageSizeOptions: [10, 20, 50, 100],
|
||||
},
|
||||
checkbox: {
|
||||
enabled: true,
|
||||
multiple: true,
|
||||
position: "left",
|
||||
selectAll: true,
|
||||
},
|
||||
horizontalScroll: { enabled: false },
|
||||
filter: { enabled: false, filters: [] },
|
||||
actions: { showActions: false, actions: [], bulkActions: false, bulkActionList: [] },
|
||||
tableStyle: { theme: "default", headerStyle: "default", rowHeight: "normal", alternateRows: false, hoverEffect: true, borderStyle: "light" },
|
||||
},
|
||||
"table-search-widget": { type: "table-search-widget", webType: "custom" },
|
||||
"split-panel-layout": { type: "split-panel-layout", webType: "text", autoLoad: true, resizable: true, splitRatio: 30 },
|
||||
"v2-split-panel-layout": { type: "v2-split-panel-layout", webType: "custom" },
|
||||
"tabs-widget": { type: "tabs-widget", webType: "text", tabs: [] },
|
||||
"v2-tabs-widget": { type: "v2-tabs-widget", webType: "custom", tabs: [] },
|
||||
"flow-widget": { type: "flow-widget", webType: "text", displayMode: "horizontal", allowDataMove: false, showStepCount: true },
|
||||
"entity-search-input": { type: "entity-search-input", webType: "entity" },
|
||||
"autocomplete-search-input": { type: "autocomplete-search-input", webType: "entity" },
|
||||
"unified-list": { type: "unified-list", webType: "table" },
|
||||
"modal-repeater-table": { type: "modal-repeater-table", webType: "table", columns: [], multiSelect: true },
|
||||
"category-manager": { type: "category-manager", webType: "custom" },
|
||||
"numbering-rule": { type: "numbering-rule", webType: "text" },
|
||||
"conditional-container": { type: "conditional-container", webType: "custom" },
|
||||
"selected-items-detail-input": { type: "selected-items-detail-input", webType: "custom" },
|
||||
"text-display": { type: "text-display", webType: "text" },
|
||||
"image-widget": { type: "image-widget", webType: "image" },
|
||||
"textarea-basic": { type: "textarea-basic", webType: "textarea", placeholder: "내용을 입력하세요" },
|
||||
"checkbox-basic": { type: "checkbox-basic", webType: "checkbox" },
|
||||
"radio-basic": { type: "radio-basic", webType: "radio" },
|
||||
"divider-line": { type: "divider-line", webType: "custom" },
|
||||
"section-paper": { type: "section-paper", webType: "custom" },
|
||||
"section-card": { type: "section-card", webType: "custom" },
|
||||
"card-display": { type: "card-display", webType: "custom" },
|
||||
"pivot-grid": { type: "pivot-grid", webType: "table" },
|
||||
"rack-structure": { type: "rack-structure", webType: "custom" },
|
||||
"v2-rack-structure": { type: "v2-rack-structure", webType: "custom" },
|
||||
"location-swap-selector": { type: "location-swap-selector", webType: "custom" },
|
||||
"screen-split-panel": { type: "screen-split-panel", webType: "custom" },
|
||||
"universal-form-modal": { type: "universal-form-modal", webType: "custom" },
|
||||
"repeater-field-group": { type: "repeater-field-group", webType: "custom" },
|
||||
"repeat-screen-modal": { type: "repeat-screen-modal", webType: "custom" },
|
||||
"related-data-buttons": { type: "related-data-buttons", webType: "custom" },
|
||||
"split-panel-layout2": { type: "split-panel-layout2", webType: "custom" },
|
||||
"unified-input": { type: "unified-input", webType: "text" },
|
||||
"unified-select": { type: "unified-select", webType: "select" },
|
||||
"unified-date": { type: "unified-date", webType: "date" },
|
||||
"unified-repeater": { type: "unified-repeater", webType: "custom" },
|
||||
"v2-repeat-container": { type: "v2-repeat-container", webType: "custom" },
|
||||
};
|
||||
|
||||
/**
|
||||
* 컴포넌트 기본값 조회
|
||||
*/
|
||||
export function getComponentDefaults(componentType: string): any {
|
||||
return componentDefaults[componentType] || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* 설정 복원: 기본값 + overrides 병합
|
||||
*
|
||||
* @param componentType 컴포넌트 타입
|
||||
* @param overrides 저장된 차이값 (config_overrides)
|
||||
* @returns 복원된 전체 설정
|
||||
*/
|
||||
export function reconstructConfig(componentType: string, overrides: any): any {
|
||||
const defaults = getComponentDefaults(componentType);
|
||||
|
||||
if (!overrides || Object.keys(overrides).length === 0) {
|
||||
return { ...defaults };
|
||||
}
|
||||
|
||||
// _originalKeys가 있으면 해당 키만 복원
|
||||
const originalKeys = overrides._originalKeys;
|
||||
|
||||
if (originalKeys && Array.isArray(originalKeys)) {
|
||||
const result: any = {};
|
||||
for (const key of originalKeys) {
|
||||
if (key === "_originalKeys") continue;
|
||||
if (Object.prototype.hasOwnProperty.call(overrides, key)) {
|
||||
result[key] = overrides[key];
|
||||
} else if (Object.prototype.hasOwnProperty.call(defaults, key)) {
|
||||
result[key] = defaults[key];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// _originalKeys가 없으면 단순 병합
|
||||
return { ...defaults, ...overrides };
|
||||
}
|
||||
|
||||
/**
|
||||
* 깊은 비교 함수
|
||||
*/
|
||||
export function isDeepEqual(a: any, b: any): boolean {
|
||||
if (a === b) return true;
|
||||
if (a == null || b == null) return a === b;
|
||||
if (typeof a !== typeof b) return false;
|
||||
if (typeof a !== "object") return a === b;
|
||||
|
||||
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
||||
if (Array.isArray(a)) {
|
||||
if (a.length !== b.length) return false;
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if (!isDeepEqual(a[i], b[i])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const keysA = Object.keys(a);
|
||||
const keysB = Object.keys(b);
|
||||
|
||||
if (keysA.length !== keysB.length) return false;
|
||||
|
||||
for (const key of keysA) {
|
||||
if (!keysB.includes(key)) return false;
|
||||
if (!isDeepEqual(a[key], b[key])) return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* 차이값 추출: 현재 설정에서 기본값과 다른 것만 추출
|
||||
*/
|
||||
export function extractConfigDiff(componentType: string, currentConfig: any): any {
|
||||
const defaults = getComponentDefaults(componentType);
|
||||
|
||||
if (!currentConfig) return {};
|
||||
|
||||
const diff: any = {
|
||||
_originalKeys: Object.keys(currentConfig),
|
||||
};
|
||||
|
||||
for (const key of Object.keys(currentConfig)) {
|
||||
const defaultVal = defaults[key];
|
||||
const currentVal = currentConfig[key];
|
||||
|
||||
if (!isDeepEqual(defaultVal, currentVal)) {
|
||||
diff[key] = currentVal;
|
||||
}
|
||||
}
|
||||
|
||||
return diff;
|
||||
}
|
||||
Reference in New Issue
Block a user